From 4a83a0fe2943e2db9941391c3d08eb3b6fdf2310 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 6 Dec 2023 16:13:12 -0800 Subject: [Parser] Parse tables and element segments (#6147) These module fields are especially complex to parse because they contain both nontrivial types and instructions, so their parsing logic needs to be spread out across the ParseDecls, ParseModuleTypes, and ParseDefs phases of parsing. This applies to in-line elements in table definitions as well, which means we need to be able to match a table to its in-line element segment across multiple phases. --- src/parser/context-decls.cpp | 75 +++++++++++- src/parser/context-defs.cpp | 32 +++++- src/parser/contexts.h | 163 +++++++++++++++++++++++--- src/parser/parsers.h | 267 ++++++++++++++++++++++++++++++++++++++++++- src/parser/wat-parser.cpp | 20 +++- src/support/result.h | 3 + 6 files changed, 540 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/parser/context-decls.cpp b/src/parser/context-decls.cpp index 721127614..7d2d8a0a9 100644 --- a/src/parser/context-decls.cpp +++ b/src/parser/context-decls.cpp @@ -77,6 +77,59 @@ Result<> ParseDeclsCtx::addFunc(Name name, return Ok{}; } +Result ParseDeclsCtx::addTableDecl(Index pos, + Name name, + ImportNames* importNames, + Limits limits) { + auto t = std::make_unique(); + t->initial = limits.initial; + t->max = limits.max ? *limits.max : Table::kUnlimitedSize; + if (name.is()) { + if (wasm.getTableOrNull(name)) { + // TODO: if the existing table is not explicitly named, fix its name and + // continue. + return in.err(pos, "repeated table name"); + } + t->setExplicitName(name); + } else { + name = (importNames ? "timport$" : "") + std::to_string(tableCounter++); + name = Names::getValidTableName(wasm, name); + t->name = name; + } + applyImportNames(*t, importNames); + return wasm.addTable(std::move(t)); +} + +Result<> ParseDeclsCtx::addTable(Name name, + const std::vector& exports, + ImportNames* import, + Limits limits, + Index pos) { + CHECK_ERR(checkImport(pos, import)); + auto t = addTableDecl(pos, name, import, limits); + CHECK_ERR(t); + CHECK_ERR(addExports(in, wasm, *t, exports, ExternalKind::Table)); + tableDefs.push_back({name, pos, Index(tableDefs.size())}); + return Ok{}; +} + +Result<> ParseDeclsCtx::addImplicitElems(TypeT, ElemListT&& elems) { + auto& table = *wasm.tables.back(); + auto e = std::make_unique(); + e->table = table.name; + e->offset = Builder(wasm).makeConstPtr(0, Type::i32); + e->name = Names::getValidElementSegmentName(wasm, "implicit-elem"); + wasm.addElementSegment(std::move(e)); + + // Record the index mapping so we can find this segment again to set its type + // and elements in later phases. + Index tableIndex = wasm.tables.size() - 1; + Index elemIndex = wasm.elementSegments.size() - 1; + implicitElemIndices[tableIndex] = elemIndex; + + return Ok{}; +} + Result ParseDeclsCtx::addMemoryDecl(Index pos, Name name, ImportNames* importNames, @@ -84,7 +137,7 @@ Result ParseDeclsCtx::addMemoryDecl(Index pos, auto m = std::make_unique(); m->indexType = type.type; m->initial = type.limits.initial; - m->max = type.limits.max; + m->max = type.limits.max ? *type.limits.max : Memory::kUnlimitedSize; m->shared = type.shared; if (name) { // TODO: if the existing memory is not explicitly named, fix its name @@ -160,6 +213,26 @@ Result<> ParseDeclsCtx::addGlobal(Name name, return Ok{}; } +Result<> ParseDeclsCtx::addElem( + Name name, TableIdxT*, std::optional, ElemListT&&, Index pos) { + auto e = std::make_unique(); + if (name) { + if (wasm.getElementSegmentOrNull(name)) { + // TDOO: if the existing segment is not explicitly named, fix its name and + // continue. + return in.err(pos, "repeated element segment name"); + } + e->setExplicitName(name); + } else { + name = std::to_string(elemCounter++); + name = Names::getValidElementSegmentName(wasm, name); + e->name = name; + } + elemDefs.push_back({name, pos, Index(wasm.elementSegments.size())}); + wasm.addElementSegment(std::move(e)); + return Ok{}; +} + Result<> ParseDeclsCtx::addData(Name name, MemoryIdxT*, std::optional, diff --git a/src/parser/context-defs.cpp b/src/parser/context-defs.cpp index 76e61d4fb..e5c598b6b 100644 --- a/src/parser/context-defs.cpp +++ b/src/parser/context-defs.cpp @@ -72,6 +72,36 @@ Result<> ParseDefsCtx::addGlobal(Name, return Ok{}; } +Result<> ParseDefsCtx::addImplicitElems(Type, + std::vector&& elems) { + auto& e = wasm.elementSegments[implicitElemIndices.at(index)]; + e->data = std::move(elems); + return Ok{}; +} + +Result<> ParseDefsCtx::addElem(Name, + Name* table, + std::optional offset, + std::vector&& elems, + Index pos) { + auto& e = wasm.elementSegments[index]; + if (offset) { + e->offset = *offset; + if (table) { + e->table = *table; + } else if (wasm.tables.size() > 0) { + e->table = wasm.tables[0]->name; + } else { + return in.err(pos, "active element segment with no table"); + } + } else { + e->offset = nullptr; + e->table = Name(); + } + e->data = std::move(elems); + return Ok{}; +} + Result<> ParseDefsCtx::addData( Name, Name* mem, std::optional offset, DataStringT, Index pos) { auto& d = wasm.dataSegments[index]; @@ -83,7 +113,7 @@ Result<> ParseDefsCtx::addData( } else if (wasm.memories.size() > 0) { d->memory = wasm.memories[0]->name; } else { - return in.err(pos, "active segment with no memory"); + return in.err(pos, "active data segment with no memory"); } } else { d->isPassive = true; diff --git a/src/parser/contexts.h b/src/parser/contexts.h index adb7694f6..8395f4dc4 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -41,7 +41,7 @@ inline std::vector getUnnamedTypes(const std::vector& named) { struct Limits { uint64_t initial; - uint64_t max; + std::optional max; }; struct MemType { @@ -94,6 +94,7 @@ struct NullTypeParserCtx { using GlobalTypeT = Ok; using TypeUseT = Ok; using LocalsT = Ok; + using ElemListT = Ok; using DataStringT = Ok; HeapTypeT makeFunc() { return Ok{}; } @@ -265,7 +266,7 @@ template struct TypeParserCtx { DataStringT makeDataString() { return Ok{}; } void appendDataString(DataStringT&, std::string_view) {} - LimitsT makeLimits(uint64_t, std::optional) { return Ok{}; } + Result makeLimits(uint64_t, std::optional) { return Ok{}; } LimitsT getLimitsFromData(DataStringT) { return Ok{}; } MemTypeT makeMemType(Type, LimitsT, bool) { return Ok{}; } @@ -282,6 +283,7 @@ struct NullInstrParserCtx { using FieldIdxT = Ok; using FuncIdxT = Ok; using LocalIdxT = Ok; + using TableIdxT = Ok; using GlobalIdxT = Ok; using MemoryIdxT = Ok; using DataIdxT = Ok; @@ -304,6 +306,8 @@ struct NullInstrParserCtx { LocalIdxT getLocalFromName(Name) { return Ok{}; } GlobalIdxT getGlobalFromIdx(uint32_t) { return Ok{}; } GlobalIdxT getGlobalFromName(Name) { return Ok{}; } + TableIdxT getTableFromIdx(uint32_t) { return Ok{}; } + TableIdxT getTableFromName(Name) { return Ok{}; } MemoryIdxT getMemoryFromIdx(uint32_t) { return Ok{}; } MemoryIdxT getMemoryFromName(Name) { return Ok{}; } DataIdxT getDataFromIdx(uint32_t) { return Ok{}; } @@ -470,8 +474,11 @@ struct NullInstrParserCtx { // Phase 1: Parse definition spans for top-level module elements and determine // their indices and names. struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { - using DataStringT = std::vector; + using ExprT = Ok; using LimitsT = Limits; + using ElemListT = Index; + using DataStringT = std::vector; + using TableTypeT = Limits; using MemTypeT = MemType; ParseInput in; @@ -488,18 +495,27 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { std::vector typeDefs; std::vector subtypeDefs; std::vector funcDefs; + std::vector tableDefs; std::vector memoryDefs; std::vector globalDefs; + std::vector elemDefs; std::vector dataDefs; std::vector tagDefs; // Positions of typeuses that might implicitly define new types. std::vector implicitTypeDefs; + // Map table indices to the indices of their implicit, in-line element + // segments. We need these to find associated segments in later parsing phases + // where we can parse their types and instructions. + std::unordered_map implicitElemIndices; + // Counters used for generating names for module elements. int funcCounter = 0; + int tableCounter = 0; int memoryCounter = 0; int globalCounter = 0; + int elemCounter = 0; int dataCounter = 0; int tagCounter = 0; @@ -534,14 +550,24 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { typeDefs.push_back({{}, pos, Index(typeDefs.size())}); } + Limits makeLimits(uint64_t n, std::optional m) { + return Limits{n, m}; + } + + Index makeElemList(TypeT) { return 0; } + Index makeFuncElemList() { return 0; } + void appendElem(Index& elems, ExprT) { ++elems; } + void appendFuncElem(Index& elems, FuncIdxT) { ++elems; } + + Limits getLimitsFromElems(Index elems) { return {elems, elems}; } + + Limits makeTableType(Limits limits, TypeT) { return limits; } + std::vector makeDataString() { return {}; } void appendDataString(std::vector& data, std::string_view str) { data.insert(data.end(), str.begin(), str.end()); } - Limits makeLimits(uint64_t n, std::optional m) { - return m ? Limits{n, *m} : Limits{n, Memory::kUnlimitedSize}; - } Limits getLimitsFromData(const std::vector& data) { uint64_t size = (data.size() + Memory::kPageSize - 1) / Memory::kPageSize; return {size, size}; @@ -567,6 +593,14 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { std::optional, Index pos); + Result + addTableDecl(Index pos, Name name, ImportNames* importNames, Limits limits); + Result<> + addTable(Name, const std::vector&, ImportNames*, Limits, Index); + + // TODO: Record index of implicit elem for use when parsing types and instrs. + Result<> addImplicitElems(TypeT, ElemListT&& elems); + Result addMemoryDecl(Index pos, Name name, ImportNames* importNames, MemType type); @@ -587,6 +621,10 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { std::optional, Index pos); + Result<> addElem(Name, TableIdxT*, std::optional, ElemListT&&, Index); + + Result<> addDeclareElem(Name, ElemListT&&, Index) { return Ok{}; } + Result<> addData(Name name, MemoryIdxT*, std::optional, @@ -741,25 +779,32 @@ struct ParseModuleTypesCtx : TypeParserCtx, // validate them when they are used. using GlobalTypeT = GlobalType; + using TableTypeT = Type; using TypeUseT = TypeUse; + using ElemListT = Type; + ParseInput in; Module& wasm; const std::vector& types; const std::unordered_map& implicitTypes; + const std::unordered_map& implicitElemIndices; // The index of the current type. Index index = 0; - ParseModuleTypesCtx(std::string_view in, - Module& wasm, - const std::vector& types, - const std::unordered_map& implicitTypes, - const IndexMap& typeIndices) + ParseModuleTypesCtx( + std::string_view in, + Module& wasm, + const std::vector& types, + const std::unordered_map& implicitTypes, + const std::unordered_map& implicitElemIndices, + const IndexMap& typeIndices) : TypeParserCtx(typeIndices), in(in), wasm(wasm), - types(types), implicitTypes(implicitTypes) {} + types(types), implicitTypes(implicitTypes), + implicitElemIndices(implicitElemIndices) {} Result getHeapTypeFromIdx(Index idx) { if (idx >= types.size()) { @@ -804,6 +849,18 @@ struct ParseModuleTypesCtx : TypeParserCtx, return {mutability, type}; } + Type makeElemList(Type type) { return type; } + Type makeFuncElemList() { return Type(HeapType::func, Nullable); } + void appendElem(ElemListT&, ExprT) {} + void appendFuncElem(ElemListT&, FuncIdxT) {} + + LimitsT getLimitsFromElems(ElemListT) { return Ok{}; } + + Type makeTableType(LimitsT, Type type) { return type; } + + LimitsT getLimitsFromData(DataStringT) { return Ok{}; } + MemTypeT makeMemType(Type, LimitsT, bool) { return Ok{}; } + Result<> addFunc(Name name, const std::vector&, ImportNames*, @@ -828,6 +885,23 @@ struct ParseModuleTypesCtx : TypeParserCtx, return Ok{}; } + Result<> addTable( + Name, const std::vector&, ImportNames*, Type ttype, Index pos) { + auto& t = wasm.tables[index]; + if (!ttype.isRef()) { + return in.err(pos, "expected reference type"); + } + t->type = ttype; + return Ok{}; + } + + Result<> addImplicitElems(Type type, ElemListT&&) { + auto& t = wasm.tables[index]; + auto& e = wasm.elementSegments[implicitElemIndices.at(index)]; + e->type = t->type; + return Ok{}; + } + Result<> addMemory(Name, const std::vector&, ImportNames*, MemTypeT, Index) { return Ok{}; @@ -847,6 +921,15 @@ struct ParseModuleTypesCtx : TypeParserCtx, return Ok{}; } + Result<> + addElem(Name, TableIdxT*, std::optional, ElemListT&& type, Index) { + auto& e = wasm.elementSegments[index]; + e->type = type; + return Ok{}; + } + + Result<> addDeclareElem(Name, ElemListT&&, Index) { return Ok{}; } + Result<> addTag(Name, const std::vector&, ImportNames*, TypeUse use, Index pos) { auto& t = wasm.tags[index]; @@ -861,15 +944,18 @@ struct ParseModuleTypesCtx : TypeParserCtx, // Phase 5: Parse module element definitions, including instructions. struct ParseDefsCtx : TypeParserCtx { using GlobalTypeT = Ok; + using TableTypeT = Ok; using TypeUseT = HeapType; using ExprT = Expression*; + using ElemListT = std::vector; using FieldIdxT = Index; using FuncIdxT = Name; using LocalIdxT = Index; using LabelIdxT = Index; using GlobalIdxT = Name; + using TableIdxT = Name; using MemoryIdxT = Name; using DataIdxT = Name; using TagIdxT = Name; @@ -883,6 +969,7 @@ struct ParseDefsCtx : TypeParserCtx { const std::vector& types; const std::unordered_map& implicitTypes; + const std::unordered_map& implicitElemIndices; // The index of the current module element. Index index = 0; @@ -903,9 +990,11 @@ struct ParseDefsCtx : TypeParserCtx { Module& wasm, const std::vector& types, const std::unordered_map& implicitTypes, + const std::unordered_map& implicitElemIndices, const IndexMap& typeIndices) : TypeParserCtx(typeIndices), in(in), wasm(wasm), builder(wasm), - types(types), implicitTypes(implicitTypes), irBuilder(wasm) {} + types(types), implicitTypes(implicitTypes), + implicitElemIndices(implicitElemIndices), irBuilder(wasm) {} template Result withLoc(Index pos, Result res) { if (auto err = res.getErr()) { @@ -929,6 +1018,20 @@ struct ParseDefsCtx : TypeParserCtx { GlobalTypeT makeGlobalType(Mutability, TypeT) { return Ok{}; } + std::vector makeElemList(TypeT) { return {}; } + std::vector makeFuncElemList() { return {}; } + void appendElem(std::vector& elems, Expression* expr) { + elems.push_back(expr); + } + void appendFuncElem(std::vector& elems, Name func) { + auto type = wasm.getFunction(func)->type; + elems.push_back(builder.makeRefFunc(func, type)); + } + + LimitsT getLimitsFromElems(std::vector& elems) { return Ok{}; } + + TableTypeT makeTableType(LimitsT, Type) { return Ok{}; } + Result getHeapTypeFromIdx(Index idx) { if (idx >= types.size()) { return in.err("type index out of bounds"); @@ -999,6 +1102,20 @@ struct ParseDefsCtx : TypeParserCtx { return name; } + Result getTableFromIdx(uint32_t idx) { + if (idx >= wasm.tables.size()) { + return in.err("table index out of bounds"); + } + return wasm.tables[idx]->name; + } + + Result getTableFromName(Name name) { + if (!wasm.getTableOrNull(name)) { + return in.err("table $" + name.toString() + " does not exist"); + } + return name; + } + Result getMemoryFromIdx(uint32_t idx) { if (idx >= wasm.memories.size()) { return in.err("memory index out of bounds"); @@ -1058,12 +1175,32 @@ struct ParseDefsCtx : TypeParserCtx { std::optional, Index pos); + Result<> + addTable(Name, const std::vector&, ImportNames*, TableTypeT, Index) { + return Ok{}; + } + Result<> addGlobal(Name, const std::vector&, ImportNames*, GlobalTypeT, std::optional exp, Index); + + Result<> addImplicitElems(Type type, std::vector&& elems); + + Result<> addDeclareElem(Name, std::vector&&, Index) { + // TODO: Validate that referenced functions appear in a declaratve element + // segment. + return Ok{}; + } + + Result<> addElem(Name, + Name* table, + std::optional offset, + std::vector&& elems, + Index pos); + Result<> addData(Name, Name* mem, std::optional offset, DataStringT, Index pos); diff --git a/src/parser/parsers.h b/src/parser/parsers.h index f16cca2b5..2bf9915eb 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -39,6 +39,7 @@ template MaybeResult arraytype(Ctx&); template Result limits32(Ctx&); template Result limits64(Ctx&); template Result memtype(Ctx&); +template Result tabletype(Ctx&); template Result globaltype(Ctx&); // Instructions @@ -165,7 +166,11 @@ template MaybeResult maybeTypeidx(Ctx& ctx); template Result typeidx(Ctx&); template Result fieldidx(Ctx&, typename Ctx::HeapTypeT); +template MaybeResult maybeFuncidx(Ctx&); template Result funcidx(Ctx&); +template MaybeResult maybeTableidx(Ctx&); +template Result tableidx(Ctx&); +template MaybeResult maybeTableuse(Ctx&); template MaybeResult maybeMemidx(Ctx&); template Result memidx(Ctx&); template MaybeResult maybeMemuse(Ctx&); @@ -182,8 +187,12 @@ template MaybeResult subtype(Ctx&); template MaybeResult<> deftype(Ctx&); template MaybeResult locals(Ctx&); template MaybeResult<> func(Ctx&); +template MaybeResult<> table(Ctx&); template MaybeResult<> memory(Ctx&); template MaybeResult<> global(Ctx&); +template MaybeResult maybeElemexpr(Ctx&); +template Result elemlist(Ctx&, bool); +template MaybeResult<> elem(Ctx&); template Result datastring(Ctx&); template MaybeResult<> data(Ctx&); template MaybeResult<> tag(Ctx&); @@ -539,6 +548,18 @@ template Result memtype(Ctx& ctx) { return ctx.makeMemType(type, *limits, shared); } +// tabletype ::= limits32 reftype +template Result tabletype(Ctx& ctx) { + auto limits = limits32(ctx); + CHECK_ERR(limits); + auto type = reftype(ctx); + CHECK_ERR(type); + if (!type) { + return ctx.in.err("expected reftype"); + } + return ctx.makeTableType(*limits, *type); +} + // globaltype ::= t:valtype => const t // | '(' 'mut' t:valtype ')' => var t template Result globaltype(Ctx& ctx) { @@ -626,7 +647,7 @@ template MaybeResult<> instr(Ctx& ctx) { if (auto keyword = tok->getKeyword()) { if (keyword == "end"sv || keyword == "then"sv || keyword == "else"sv || keyword == "catch"sv || keyword == "catch_all"sv || - keyword == "delegate"sv) { + keyword == "delegate"sv || keyword == "ref"sv) { return {}; } } @@ -1693,16 +1714,60 @@ Result fieldidx(Ctx& ctx, // funcidx ::= x:u32 => x // | v:id => x (if t.funcs[x] = v) -template Result funcidx(Ctx& ctx) { +template +MaybeResult maybeFuncidx(Ctx& ctx) { if (auto x = ctx.in.takeU32()) { return ctx.getFuncFromIdx(*x); } if (auto id = ctx.in.takeID()) { return ctx.getFuncFromName(*id); } + return {}; +} + +template Result funcidx(Ctx& ctx) { + if (auto idx = maybeFuncidx(ctx)) { + CHECK_ERR(idx); + return *idx; + } return ctx.in.err("expected function index or identifier"); } +// tableidx ::= x:u23 => x +// | v:id => x (if tables[x] = v) +template +MaybeResult maybeTableidx(Ctx& ctx) { + if (auto x = ctx.in.takeU32()) { + return ctx.getTableFromIdx(*x); + } + if (auto id = ctx.in.takeID()) { + return ctx.getTableFromName(*id); + } + return {}; +} + +template Result tableidx(Ctx& ctx) { + if (auto idx = maybeTableidx(ctx)) { + CHECK_ERR(idx); + return *idx; + } + return ctx.in.err("expected table index or identifier"); +} + +// tableuse ::= '(' 'table' x:tableidx ')' +template +MaybeResult maybeTableuse(Ctx& ctx) { + if (!ctx.in.takeSExprStart("table"sv)) { + return {}; + } + auto idx = tableidx(ctx); + CHECK_ERR(idx); + if (!ctx.in.takeRParen()) { + return ctx.in.err("Expected end of memory use"); + } + return *idx; +} + // memidx ::= x:u32 => x // | v:id => x (if memories[x] = v) template @@ -2031,6 +2096,82 @@ template MaybeResult<> func(Ctx& ctx) { return Ok{}; } +// table ::= '(' 'table' id? ('(' 'export' name ')')* +// '(' 'import' mod:name nm:name ')'? tabletype ')' +// | '(' 'table' id? ('(' 'export' name ')')* +// reftype '(' 'elem' (elemexpr* | funcidx*) ')' ')' +template MaybeResult<> table(Ctx& ctx) { + auto pos = ctx.in.getPos(); + if (!ctx.in.takeSExprStart("table"sv)) { + return {}; + } + + Name name; + if (auto id = ctx.in.takeID()) { + name = *id; + } + + auto exports = inlineExports(ctx.in); + CHECK_ERR(exports); + + auto import = inlineImport(ctx.in); + CHECK_ERR(import); + + // Reftype if we have inline elements. + auto type = reftype(ctx); + CHECK_ERR(type); + + std::optional ttype; + std::optional elems; + if (type) { + // We should have inline elements. + if (!ctx.in.takeSExprStart("elem"sv)) { + return ctx.in.err("expected table limits or inline elements"); + } + if (import) { + return ctx.in.err("imported tables cannot have inline elements"); + } + + auto list = ctx.makeElemList(*type); + bool foundElem = false; + while (auto elem = maybeElemexpr(ctx)) { + CHECK_ERR(elem); + ctx.appendElem(list, *elem); + foundElem = true; + } + + // If there were no elemexprs, then maybe we have funcidxs instead. + if (!foundElem) { + while (auto func = maybeFuncidx(ctx)) { + CHECK_ERR(func); + ctx.appendFuncElem(list, *func); + } + } + + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of inline elems"); + } + ttype = ctx.makeTableType(ctx.getLimitsFromElems(list), *type); + elems = std::move(list); + } else { + auto tabtype = tabletype(ctx); + CHECK_ERR(tabtype); + ttype = *tabtype; + } + + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of table declaration"); + } + + CHECK_ERR(ctx.addTable(name, *exports, import.getPtr(), *ttype, pos)); + + if (elems) { + CHECK_ERR(ctx.addImplicitElems(*type, std::move(*elems))); + } + + return Ok{}; +} + // mem ::= '(' 'memory' id? ('(' 'export' name ')')* // ('(' 'data' b:datastring ')' | memtype) ')' // | '(' 'memory' id? ('(' 'export' name ')')* @@ -2122,6 +2263,120 @@ template MaybeResult<> global(Ctx& ctx) { return Ok{}; } +// elemexpr ::= '(' 'item' expr ')' | '(' instr ')' +template +MaybeResult maybeElemexpr(Ctx& ctx) { + MaybeResult result; + if (ctx.in.takeSExprStart("item"sv)) { + result = expr(ctx); + } else if (ctx.in.takeLParen()) { + // TODO: `instr` should included both folded and unfolded instrs. + if (auto inst = instr(ctx)) { + CHECK_ERR(inst); + } else { + return ctx.in.err("expected instruction"); + } + result = ctx.makeExpr(); + } else { + return {}; + } + CHECK_ERR(result); + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of element expression"); + } + return result; +} + +// elemlist ::= reftype elemexpr* | 'func' funcidx* +// | funcidx* (iff the tableuse is omitted) +template +Result elemlist(Ctx& ctx, bool legacy) { + if (auto type = reftype(ctx)) { + auto res = ctx.makeElemList(*type); + while (auto elem = maybeElemexpr(ctx)) { + CHECK_ERR(elem); + ctx.appendElem(res, *elem); + } + return res; + } else if (ctx.in.takeKeyword("func"sv) || legacy) { + auto res = ctx.makeFuncElemList(); + while (auto func = maybeFuncidx(ctx)) { + CHECK_ERR(func); + ctx.appendFuncElem(res, *func); + } + return res; + } + return ctx.in.err("expected element list"); +} + +// elem ::= '(' 'elem' id? x:tableuse? ('(' ('offset' e:expr | e:instr) ')')? +// elemlist ')' +// | '(' 'elem' id? 'declare' elemlist ')' +template MaybeResult<> elem(Ctx& ctx) { + auto pos = ctx.in.getPos(); + if (!ctx.in.takeSExprStart("elem"sv)) { + return {}; + } + + Name name; + if (auto id = ctx.in.takeID()) { + name = *id; + } + + bool isDeclare = false; + MaybeResult table; + std::optional offset; + + if (ctx.in.takeKeyword("declare"sv)) { + isDeclare = true; + } else { + table = maybeTableuse(ctx); + CHECK_ERR(table); + + if (ctx.in.takeSExprStart("offset")) { + auto off = expr(ctx); + CHECK_ERR(off); + offset = *off; + } else { + // This may be an abbreviated offset instruction or it may be the + // beginning of the elemlist. + auto beforeLParen = ctx.in.getPos(); + if (ctx.in.takeLParen()) { + if (auto inst = instr(ctx)) { + CHECK_ERR(inst); + auto off = ctx.makeExpr(); + CHECK_ERR(off); + offset = *off; + } else { + // This must be the beginning of the elemlist instead. + ctx.in.lexer.setIndex(beforeLParen); + } + } + } + if (offset && !ctx.in.takeRParen()) { + return ctx.in.err("expected end of offset expression"); + } + } + + // If there is no explicit tableuse, we can use the legacy elemlist format. + bool legacy = !table; + auto elems = elemlist(ctx, legacy); + CHECK_ERR(elems); + + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of element segment"); + } + + if (isDeclare) { + CHECK_ERR(ctx.addDeclareElem(name, std::move(*elems), pos)); + } else { + CHECK_ERR( + ctx.addElem(name, table.getPtr(), offset, std::move(*elems), pos)); + } + + return Ok{}; +} + // datastring ::= (b:string)* => concat(b*) template Result datastring(Ctx& ctx) { auto data = ctx.makeDataString(); @@ -2236,6 +2491,10 @@ template MaybeResult<> modulefield(Ctx& ctx) { CHECK_ERR(res); return Ok{}; } + if (auto res = table(ctx)) { + CHECK_ERR(res); + return Ok{}; + } if (auto res = memory(ctx)) { CHECK_ERR(res); return Ok{}; @@ -2244,6 +2503,10 @@ template MaybeResult<> modulefield(Ctx& ctx) { CHECK_ERR(res); return Ok{}; } + if (auto res = elem(ctx)) { + CHECK_ERR(res); + return Ok{}; + } if (auto res = data(ctx)) { CHECK_ERR(res); return Ok{}; diff --git a/src/parser/wat-parser.cpp b/src/parser/wat-parser.cpp index be3bca5c8..cc0f582fc 100644 --- a/src/parser/wat-parser.cpp +++ b/src/parser/wat-parser.cpp @@ -132,6 +132,7 @@ Result<> parseModule(Module& wasm, std::string_view input) { // Parse implicit type definitions and map typeuses without explicit types to // the correct types. std::unordered_map implicitTypes; + { ParseImplicitTypeDefsCtx ctx(input, types, implicitTypes, *typeIndices); for (Index pos : decls.implicitTypeDefs) { @@ -142,18 +143,31 @@ Result<> parseModule(Module& wasm, std::string_view input) { { // Parse module-level types. - ParseModuleTypesCtx ctx(input, wasm, types, implicitTypes, *typeIndices); + ParseModuleTypesCtx ctx(input, + wasm, + types, + implicitTypes, + decls.implicitElemIndices, + *typeIndices); CHECK_ERR(parseDefs(ctx, decls.funcDefs, func)); + CHECK_ERR(parseDefs(ctx, decls.tableDefs, table)); CHECK_ERR(parseDefs(ctx, decls.memoryDefs, memory)); CHECK_ERR(parseDefs(ctx, decls.globalDefs, global)); + CHECK_ERR(parseDefs(ctx, decls.elemDefs, elem)); CHECK_ERR(parseDefs(ctx, decls.tagDefs, tag)); - // TODO: Parse types of other module elements. } { // Parse definitions. // TODO: Parallelize this. - ParseDefsCtx ctx(input, wasm, types, implicitTypes, *typeIndices); + ParseDefsCtx ctx(input, + wasm, + types, + implicitTypes, + decls.implicitElemIndices, + *typeIndices); + CHECK_ERR(parseDefs(ctx, decls.tableDefs, table)); CHECK_ERR(parseDefs(ctx, decls.globalDefs, global)); + CHECK_ERR(parseDefs(ctx, decls.elemDefs, elem)); CHECK_ERR(parseDefs(ctx, decls.dataDefs, data)); for (Index i = 0; i < decls.funcDefs.size(); ++i) { diff --git a/src/support/result.h b/src/support/result.h index acbf92966..ab71d3a53 100644 --- a/src/support/result.h +++ b/src/support/result.h @@ -75,6 +75,9 @@ template struct MaybeResult { // conditions where errors should not get lost. operator bool() const { return !std::holds_alternative(val); } + MaybeResult& operator=(const MaybeResult&) = default; + MaybeResult& operator=(MaybeResult&&) = default; + Err* getErr() { return std::get_if(&val); } T& operator*() { return *std::get_if(&val); } T* operator->() { return std::get_if(&val); } -- cgit v1.2.3