diff options
-rw-r--r-- | src/tools/wasm-opt.cpp | 6 | ||||
-rw-r--r-- | src/wasm-io.h | 4 | ||||
-rw-r--r-- | src/wasm/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/wasm/wasm-io.cpp | 20 | ||||
-rw-r--r-- | src/wasm/wat-parser.cpp | 651 | ||||
-rw-r--r-- | src/wat-lexer.h | 17 | ||||
-rw-r--r-- | src/wat-parser.h | 73 | ||||
-rw-r--r-- | test/gtest/wat-lexer.cpp | 2 | ||||
-rw-r--r-- | test/lit/help/wasm-opt.test | 3 | ||||
-rw-r--r-- | test/lit/wat-kitchen-sink.wast | 18 |
10 files changed, 785 insertions, 10 deletions
diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index c026be21b..3927e513c 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -228,6 +228,12 @@ int main(int argc, const char* argv[]) { [&outputSourceMapUrl](Options* o, const std::string& argument) { outputSourceMapUrl = argument; }) + .add("--new-wat-parser", + "", + "Use the experimental new WAT parser", + WasmOptOption, + Options::Arguments::Zero, + [](Options*, const std::string&) { useNewWATParser = true; }) .add_positional("INFILE", Options::Arguments::One, [](Options* o, const std::string& argument) { diff --git a/src/wasm-io.h b/src/wasm-io.h index 1e28b5f92..ae66c3932 100644 --- a/src/wasm-io.h +++ b/src/wasm-io.h @@ -27,6 +27,10 @@ namespace wasm { +// TODO: Remove this after switching to the new WAT parser by default and +// removing the old one. +extern bool useNewWATParser; + class ModuleIOBase { protected: bool debugInfo; diff --git a/src/wasm/CMakeLists.txt b/src/wasm/CMakeLists.txt index 974f3cd10..7b7f85698 100644 --- a/src/wasm/CMakeLists.txt +++ b/src/wasm/CMakeLists.txt @@ -13,6 +13,7 @@ set(wasm_SOURCES wasm-type.cpp wasm-validator.cpp wat-lexer.cpp + wat-parser.cpp ${wasm_HEADERS} ) # wasm-debug.cpp includes LLVM header using std::iterator (deprecated in C++17) diff --git a/src/wasm/wasm-io.cpp b/src/wasm/wasm-io.cpp index b94fc8470..55892975b 100644 --- a/src/wasm/wasm-io.cpp +++ b/src/wasm/wasm-io.cpp @@ -24,19 +24,31 @@ // binary. // -#include "wasm-io.h" #include "support/debug.h" #include "wasm-binary.h" #include "wasm-s-parser.h" +#include "wat-parser.h" + +#include "wasm-io.h" namespace wasm { +bool useNewWATParser = false; + #define DEBUG_TYPE "writer" static void readTextData(std::string& input, Module& wasm, IRProfile profile) { - SExpressionParser parser(const_cast<char*>(input.c_str())); - Element& root = *parser.root; - SExpressionWasmBuilder builder(wasm, *root[0], profile); + if (useNewWATParser) { + std::string_view in(input.c_str()); + if (auto parsed = WATParser::parseModule(wasm, in); + auto err = parsed.getErr()) { + Fatal() << err->msg; + } + } else { + SExpressionParser parser(const_cast<char*>(input.c_str())); + Element& root = *parser.root; + SExpressionWasmBuilder builder(wasm, *root[0], profile); + } } void ModuleReader::readText(std::string filename, Module& wasm) { diff --git a/src/wasm/wat-parser.cpp b/src/wasm/wat-parser.cpp new file mode 100644 index 000000000..254db79fd --- /dev/null +++ b/src/wasm/wat-parser.cpp @@ -0,0 +1,651 @@ +/* + * Copyright 2022 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 "wat-parser.h" +#include "ir/names.h" +#include "support/name.h" +#include "wasm-builder.h" +#include "wasm-type.h" +#include "wasm.h" +#include "wat-lexer.h" + +// The WebAssembly text format is recursive in the sense that elements may be +// referred to before they are declared. Furthermore, elements may be referred +// to by index or by name. As a result, we need to parse text modules in +// multiple phases. +// +// In the first phase, we find all of the module element declarations and +// record, but do not interpret, the input spans of their corresponding +// definitions. This phase establishes the indices and names of each module +// element so that subsequent phases can look them up. +// +// The second phase, not yet implemented, parses type definitions to construct +// the types used in the module. This has to be its own phase because we have no +// way to refer to a type before it has been built along with all the other +// types, unlike for other module elements that can be referred to by name +// before their definitions have been parsed. +// +// The third phase, not yet implemented, further parses and constructs types +// implicitly defined by type uses in functions, blocks, and call_indirect +// instructions. These implicitly defined types may be referred to by index +// elsewhere. +// +// The fourth phase, not yet implemented, parses and sets the types of globals, +// functions, and other top-level module elements. These types need to be set +// before we parse instructions because they determine the types of instructions +// such as global.get and ref.func. +// +// In the fifth and final phase, not yet implemented, parses the remaining +// contents of all module elements, including instructions. +// +// Each phase of parsing gets its own context type that is passed to the +// individual parsing functions. There is a parsing function for each element of +// the grammar given in the spec. Parsing functions are templatized so that they +// may be passed the appropriate context type and return the correct result type +// for each phase. + +#define RETURN_OR_OK(val) \ + if constexpr (parsingDecls<Ctx>) { \ + return Ok{}; \ + } else { \ + return val; \ + } + +#define CHECK_ERR(val) \ + if (auto _val = (val); auto err = _val.getErr()) { \ + return Err{*err}; \ + } + +using namespace std::string_view_literals; + +namespace wasm::WATParser { + +namespace { + +// ============ +// Parser Input +// ============ + +// Wraps a lexer and provides utilities for consuming tokens. +struct ParseInput { + Lexer lexer; + + ParseInput(std::string_view in) : lexer(in) {} + + ParseInput(std::string_view in, size_t index) : lexer(in) { + lexer.setIndex(index); + } + + bool empty() { return lexer == lexer.end(); } + + std::optional<Token> peek() { + if (!empty()) { + return *lexer; + } + return {}; + } + + bool takeLParen() { + auto t = peek(); + if (!t || !t->isLParen()) { + return false; + } + ++lexer; + return true; + } + + bool takeRParen() { + auto t = peek(); + if (!t || !t->isRParen()) { + return false; + } + ++lexer; + return true; + } + + std::optional<Name> takeID() { + if (auto t = peek()) { + if (auto id = t->getID()) { + ++lexer; + // See comment on takeName. + return Name(std::string(*id)); + } + } + return {}; + } + + bool takeKeyword(std::string_view expected) { + if (auto t = peek()) { + if (auto keyword = t->getKeyword()) { + if (*keyword == expected) { + ++lexer; + return true; + } + } + } + return false; + } + + std::optional<uint64_t> takeU64() { + if (auto t = peek()) { + if (auto n = t->getU64()) { + ++lexer; + return n; + } + } + return {}; + } + + std::optional<int64_t> takeS64() { + if (auto t = peek()) { + if (auto n = t->getS64()) { + ++lexer; + return n; + } + } + return {}; + } + + std::optional<int64_t> takeI64() { + if (auto t = peek()) { + if (auto n = t->getI64()) { + ++lexer; + return n; + } + } + return {}; + } + + std::optional<uint32_t> takeU32() { + if (auto t = peek()) { + if (auto n = t->getU32()) { + ++lexer; + return n; + } + } + return {}; + } + + std::optional<int32_t> takeS32() { + if (auto t = peek()) { + if (auto n = t->getS32()) { + ++lexer; + return n; + } + } + return {}; + } + + std::optional<int32_t> takeI32() { + if (auto t = peek()) { + if (auto n = t->getI32()) { + ++lexer; + return n; + } + } + return {}; + } + + std::optional<double> takeF64() { + if (auto t = peek()) { + if (auto d = t->getF64()) { + ++lexer; + return d; + } + } + return {}; + } + + std::optional<float> takeF32() { + if (auto t = peek()) { + if (auto f = t->getF32()) { + ++lexer; + return f; + } + } + return {}; + } + + std::optional<std::string_view> takeString() { + if (auto t = peek()) { + if (auto s = t->getString()) { + ++lexer; + return s; + } + } + return {}; + } + + std::optional<Name> takeName() { + // TODO: Move this to lexer and validate UTF. + if (auto str = takeString()) { + // Copy to a std::string to make sure we have a null terminator, otherwise + // the `Name` constructor won't work correctly. + // TODO: Update `Name` to use string_view instead of char* and/or to take + // rvalue strings to avoid this extra copy. + return Name(std::string(*str)); + } + return {}; + } + + bool takeSExprStart(std::string_view expected) { + auto original = lexer; + if (takeLParen() && takeKeyword(expected)) { + return true; + } + lexer = original; + return false; + } + + Index getPos() { + if (auto t = peek()) { + return lexer.getIndex() - t->span.size(); + } + return lexer.getIndex(); + } + + [[nodiscard]] Err err(std::string reason) { + std::stringstream msg; + msg << lexer.position(lexer.getIndex()) << ": error: " << reason; + return Err{msg.str()}; + } +}; + +// =================== +// POD Utility Structs +// =================== + +// The location and possible name of a module-level definition in the input. +struct DefPos { + Name name; + Index pos; +}; + +struct GlobalType { + Mutability mutability; + Type type; +}; + +struct ImportNames { + Name mod; + Name nm; +}; + +// =============== +// Parser Contexts +// =============== + +// Phase 1: Parse definition spans for top-level module elements and determine +// their indices and names. +struct ParseDeclsCtx { + // At this stage we only look at types to find implicit type definitions, + // which are inserted directly in to the context. We cannot materialize or + // validate any types because we don't know what types exist yet. + using ValTypeT = Ok; + using GlobalTypeT = Ok; + + // Declared module elements are inserted into the module, but their bodies are + // not filled out until later parsing phases. + Module& wasm; + + // The module element definitions we are parsing in this phase. + std::vector<DefPos> globalDefs; + + // Counters used for generating names for module elements. + int globalCounter = 0; + + // Used to verify that all imports come before all non-imports. + bool hasNonImport = false; + + ParseDeclsCtx(Module& wasm) : wasm(wasm) {} +}; + +template<typename Ctx> +inline constexpr bool parsingDecls = std::is_same_v<Ctx, ParseDeclsCtx>; + +// TODO: Phase 2: ParseTypeDefsCtx +// TODO: Phase 3: ParseImplicitTypeDefsCtx + +// Phase 4: Parse and set the types of module elements. +struct ParseModuleTypesCtx { + // In this phase we have constructed all the types, so we can materialize and + // validate them when they are used. + using ValTypeT = Type; + using GlobalTypeT = GlobalType; + + Module& wasm; + + // The index of the current element in its index space. + Index index = 0; + + ParseModuleTypesCtx(Module& wasm) : wasm(wasm) {} +}; + +template<typename Ctx> +inline constexpr bool parsingModuleTypes = + std::is_same_v<Ctx, ParseModuleTypesCtx>; + +// TODO: Phase 5: ParseDefsCtx + +// ================ +// Parser Functions +// ================ + +// Types +template<typename Ctx> +Result<typename Ctx::ValTypeT> valtype(Ctx&, ParseInput&); + +// Modules +MaybeResult<ImportNames> inlineImport(ParseInput&); +Result<std::vector<Name>> inlineExports(ParseInput&); +template<typename Ctx> MaybeResult<> global(Ctx&, ParseInput&); +MaybeResult<> modulefield(ParseDeclsCtx&, ParseInput&); +Result<> module(ParseDeclsCtx&, ParseInput&); + +// Utilities +template<typename T> +void applyImportNames(Importable& item, + const std::optional<ImportNames>& names); +Result<> addExports(ParseInput& in, + Module& wasm, + const Named* item, + const std::vector<Name>& exports, + ExternalKind kind); +Result<Global*> addGlobalDecl(ParseDeclsCtx& ctx, + ParseInput& in, + Name name, + std::optional<ImportNames> importNames); +template<typename Ctx> +Result<> parseDefs(Ctx& ctx, + std::string_view input, + const std::vector<DefPos>& defs, + MaybeResult<> (*parser)(Ctx&, ParseInput&)); + +// ===== +// Types +// ===== + +// numtype ::= 'i32' => i32 +// | 'i64' => i64 +// | 'f32' => f32 +// | 'f64' => f64 +// vectype ::= 'v128' => v128 +// valtype ::= t:numtype => t +// | t:vectype => t +// | t:reftype => t +template<typename Ctx> +Result<typename Ctx::ValTypeT> valtype(Ctx& ctx, ParseInput& in) { + if (in.takeKeyword("i32"sv)) { + RETURN_OR_OK(Type::i32); + } else if (in.takeKeyword("i64"sv)) { + RETURN_OR_OK(Type::i64); + } else if (in.takeKeyword("f32"sv)) { + RETURN_OR_OK(Type::f32); + } else if (in.takeKeyword("f64"sv)) { + RETURN_OR_OK(Type::f64); + } else if (in.takeKeyword("v128"sv)) { + RETURN_OR_OK(Type::v128); + } else { + // TODO: reftype + return in.err("expected valtype"); + } +} + +// globaltype ::= t:valtype => const t +// | '(' 'mut' t:valtype ')' => var t +template<typename Ctx> +Result<typename Ctx::GlobalTypeT> globaltype(Ctx& ctx, ParseInput& in) { + // '(' 'mut' t:valtype ')' + if (in.takeSExprStart("mut"sv)) { + auto type = valtype(ctx, in); + CHECK_ERR(type); + + if (!in.takeRParen()) { + return in.err("expected end of globaltype"); + } + RETURN_OR_OK((GlobalType{Mutable, *type})); + } + + // t:valtype + auto type = valtype(ctx, in); + CHECK_ERR(type); + RETURN_OR_OK((GlobalType{Immutable, *type})); +} + +// ======= +// Modules +// ======= + +// ('(' 'import' mod:name nm:name ')')? +MaybeResult<ImportNames> inlineImport(ParseInput& in) { + if (!in.takeSExprStart("import"sv)) { + return {}; + } + auto mod = in.takeName(); + if (!mod) { + return in.err("expected import module"); + } + auto nm = in.takeName(); + if (!nm) { + return in.err("expected import name"); + } + if (!in.takeRParen()) { + return in.err("expected end of import"); + } + return {{*mod, *nm}}; +} + +// ('(' 'export' name ')')* +Result<std::vector<Name>> inlineExports(ParseInput& in) { + std::vector<Name> exports; + while (in.takeSExprStart("export"sv)) { + auto name = in.takeName(); + if (!name) { + return in.err("expected export name"); + } + if (!in.takeRParen()) { + return in.err("expected end of import"); + } + exports.push_back(*name); + } + return exports; +} + +// global ::= '(' 'global' id? ('(' 'export' name ')')* gt:globaltype e:expr ')' +// | '(' 'global' id? '(' 'import' mod:name nm:name ')' +// gt:globaltype ')' +template<typename Ctx> MaybeResult<> global(Ctx& ctx, ParseInput& in) { + [[maybe_unused]] auto pos = in.getPos(); + if (!in.takeSExprStart("global"sv)) { + return {}; + } + Name name; + if (auto id = in.takeID()) { + name = *id; + } + + auto exports = inlineExports(in); + CHECK_ERR(exports); + + auto import = inlineImport(in); + CHECK_ERR(import); + + auto type = globaltype(ctx, in); + CHECK_ERR(type); + + if (import) { + if (!in.takeRParen()) { + return in.err("expected end of global"); + } + + if constexpr (parsingDecls<Ctx>) { + if (ctx.hasNonImport) { + return in.err("import after non-import"); + } + auto g = addGlobalDecl(ctx, in, name, *import); + CHECK_ERR(g); + CHECK_ERR(addExports(in, ctx.wasm, *g, *exports, ExternalKind::Global)); + ctx.globalDefs.push_back({name, pos}); + } else if constexpr (parsingModuleTypes<Ctx>) { + auto& g = ctx.wasm.globals[ctx.index]; + g->mutable_ = type->mutability; + g->type = type->type; + } + return Ok{}; + } + + return in.err("TODO: non-imported globals"); +} + +// modulefield ::= type +// | import +// | func +// | table +// | mem +// | global +// | export +// | start +// | elem +// | data +MaybeResult<> modulefield(ParseDeclsCtx& ctx, ParseInput& in) { + if (auto t = in.peek(); !t || t->isRParen()) { + return {}; + } + if (auto res = global(ctx, in)) { + CHECK_ERR(res); + return Ok{}; + } + return in.err("unrecognized module field"); +} + +// module ::= '(' 'module' id? (m:modulefield)* ')' +// | (m:modulefield)* eof +Result<> module(ParseDeclsCtx& ctx, ParseInput& in) { + bool outer = in.takeSExprStart("module"sv); + + if (outer) { + if (auto id = in.takeID()) { + ctx.wasm.name = *id; + } + } + + while (auto field = modulefield(ctx, in)) { + CHECK_ERR(field); + } + + if (outer && !in.takeRParen()) { + return in.err("expected end of module"); + } + + return Ok{}; +} + +// ========= +// Utilities +// ========= + +void applyImportNames(Importable& item, + const std::optional<ImportNames>& names) { + if (names) { + item.module = names->mod; + item.base = names->nm; + } +} + +Result<> addExports(ParseInput& in, + Module& wasm, + const Named* item, + const std::vector<Name>& exports, + ExternalKind kind) { + for (auto name : exports) { + if (wasm.getExportOrNull(name)) { + // TODO: Fix error location + return in.err("repeated export name"); + } + wasm.addExport(Builder(wasm).makeExport(name, item->name, kind)); + } + return Ok{}; +} + +Result<Global*> addGlobalDecl(ParseDeclsCtx& ctx, + ParseInput& in, + Name name, + std::optional<ImportNames> importNames) { + auto g = std::make_unique<Global>(); + if (name.is()) { + if (ctx.wasm.getGlobalOrNull(name)) { + // TODO: if the existing global is not explicitly named, fix its name and + // continue. + // TODO: Fix error location to point to name. + return in.err("repeated global name"); + } + g->setExplicitName(name); + } else { + name = + (importNames ? "gimport$" : "") + std::to_string(ctx.globalCounter++); + name = Names::getValidGlobalName(ctx.wasm, name); + g->name = name; + } + applyImportNames(*g, importNames); + return ctx.wasm.addGlobal(std::move(g)); +} + +template<typename Ctx> +Result<> parseDefs(Ctx& ctx, + std::string_view input, + const std::vector<DefPos>& defs, + MaybeResult<> (*parser)(Ctx&, ParseInput&)) { + for (Index i = 0; i < defs.size(); ++i) { + ctx.index = i; + ParseInput in(input, defs[i].pos); + auto parsed = parser(ctx, in); + CHECK_ERR(parsed); + assert(parsed); + } + return Ok{}; +} + +} // anonymous namespace + +Result<> parseModule(Module& wasm, std::string_view input) { + // Parse module-level declarations. + ParseDeclsCtx decls(wasm); + { + ParseInput in(input); + CHECK_ERR(module(decls, in)); + if (!in.empty()) { + return in.err("Unexpected tokens after module"); + } + } + + // TODO: Map names to indices. + + // TODO: Parse type definitions. + + // TODO: Parse implicit type definitions. + + // Parse module-level types. + ParseModuleTypesCtx ctx(wasm); + CHECK_ERR(parseDefs(ctx, input, decls.globalDefs, global)); + + // TODO: Parse types of other module elements. + + // TODO: Parse definitions. + + return Ok{}; +} + +} // namespace wasm::WATParser diff --git a/src/wat-lexer.h b/src/wat-lexer.h index e4ba2efa8..30aa1e036 100644 --- a/src/wat-lexer.h +++ b/src/wat-lexer.h @@ -22,8 +22,8 @@ #include <string_view> #include <variant> -#ifndef wasm_wat_parser_h -#define wasm_wat_parser_h +#ifndef wasm_wat_lexer_h +#define wasm_wat_lexer_h namespace wasm::WATParser { @@ -112,7 +112,8 @@ struct Token { std::optional<std::string_view> getID() const { if (std::get_if<IdTok>(&data)) { - return span; + // Drop leading '$'. + return span.substr(1); } return {}; } @@ -163,7 +164,12 @@ public: // The end sentinel. Lexer() = default; - Lexer(std::string_view buffer) : buffer(buffer) { + Lexer(std::string_view buffer) : buffer(buffer) { setIndex(0); } + + size_t getIndex() { return index; } + + void setIndex(size_t i) { + index = i; skipSpace(); lexToken(); skipSpace(); @@ -203,6 +209,7 @@ public: Lexer end() { return Lexer(); } TextPos position(const char* c); + TextPos position(size_t i) { return position(buffer.data() + i); } TextPos position(std::string_view span) { return position(span.data()); } TextPos position(Token tok) { return position(tok.span); } @@ -213,4 +220,4 @@ private: } // namespace wasm::WATParser -#endif // wasm_wat_parser_h +#endif // wasm_wat_lexer_h diff --git a/src/wat-parser.h b/src/wat-parser.h new file mode 100644 index 000000000..4bb9b62c8 --- /dev/null +++ b/src/wat-parser.h @@ -0,0 +1,73 @@ +/* + * Copyright 2022 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. + */ + +#ifndef wasm_wat_parser_h +#define wasm_wat_parser_h + +#include <optional> +#include <string_view> + +#include "wasm.h" + +namespace wasm::WATParser { + +struct Ok {}; + +struct None {}; + +struct Err { + std::string msg; +}; + +template<typename T = Ok> struct Result { + std::variant<T, Err> val; + + Result(Result<T>& other) = default; + Result(const Err& e) : val(std::in_place_type<Err>, e) {} + Result(Err&& e) : val(std::in_place_type<Err>, e) {} + template<typename U = T> + Result(U&& u) : val(std::in_place_type<T>, std::forward<U>(u)) {} + + Err* getErr() { return std::get_if<Err>(&val); } + T& operator*() { return *std::get_if<T>(&val); } + T* operator->() { return std::get_if<T>(&val); } +}; + +template<typename T = Ok> struct MaybeResult { + std::variant<T, None, Err> val; + + MaybeResult() : val(None{}) {} + MaybeResult(MaybeResult<T>& other) = default; + MaybeResult(const Err& e) : val(std::in_place_type<Err>, e) {} + MaybeResult(Err&& e) : val(std::in_place_type<Err>, std::move(e)) {} + template<typename U = T> + MaybeResult(U&& u) : val(std::in_place_type<T>, std::forward<U>(u)) {} + + // Whether we have an error or a value. Useful for assignment in loops and if + // conditions where errors should not get lost. + operator bool() const { return !std::holds_alternative<None>(val); } + + Err* getErr() { return std::get_if<Err>(&val); } + T& operator*() { return *std::get_if<T>(&val); } + T* operator->() { return std::get_if<T>(&val); } +}; + +// Parse a single WAT module. +Result<> parseModule(Module& wasm, std::string_view in); + +} // namespace wasm::WATParser + +#endif // wasm_wat_parser.h diff --git a/test/gtest/wat-lexer.cpp b/test/gtest/wat-lexer.cpp index a1c60a706..0f83127c8 100644 --- a/test/gtest/wat-lexer.cpp +++ b/test/gtest/wat-lexer.cpp @@ -1361,7 +1361,7 @@ TEST(LexerTest, LexIdent) { Token expected{"$09azAZ!#$%&'*+-./:<=>?@\\^_`|~"sv, IdTok{}}; EXPECT_EQ(*lexer, expected); EXPECT_TRUE(lexer->getID()); - EXPECT_EQ(*lexer->getID(), "$09azAZ!#$%&'*+-./:<=>?@\\^_`|~"sv); + EXPECT_EQ(*lexer->getID(), "09azAZ!#$%&'*+-./:<=>?@\\^_`|~"sv); } { Lexer lexer("$[]{}"sv); diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index bb111d92e..66ac66f91 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -76,6 +76,9 @@ ;; CHECK-NEXT: --output-source-map-url,-osu Emit specified string as source ;; CHECK-NEXT: map URL ;; CHECK-NEXT: +;; CHECK-NEXT: --new-wat-parser Use the experimental new WAT +;; CHECK-NEXT: parser +;; CHECK-NEXT: ;; CHECK-NEXT: ;; CHECK-NEXT: Optimization passes: ;; CHECK-NEXT: -------------------- diff --git a/test/lit/wat-kitchen-sink.wast b/test/lit/wat-kitchen-sink.wast new file mode 100644 index 000000000..90d5d5b41 --- /dev/null +++ b/test/lit/wat-kitchen-sink.wast @@ -0,0 +1,18 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt --new-wat-parser -all %s -S -o - | filecheck %s + +(module $parse + + ;; globals + (global $g1 (export "g1") (export "g1.1") (import "mod" "g1") i32) + (global $g2 (import "mod" "g2") (mut i64)) + +) +;; CHECK: (import "mod" "g1" (global $g1 i32)) + +;; CHECK: (import "mod" "g2" (global $g2 (mut i64))) + +;; CHECK: (export "g1" (global $g1)) + +;; CHECK: (export "g1.1" (global $g1)) |