blob: 32f6709dfe595ae22ff1ca7c11526fabc426ef2d [file] [log] [blame] [edit]
/*
* Copyright 2023 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 parser_parsers_h
#define parser_parsers_h
#include "common.h"
#include "input.h"
namespace wasm::WATParser {
using namespace std::string_view_literals;
// Types
template<typename Ctx> Result<typename Ctx::HeapTypeT> heaptype(Ctx&);
template<typename Ctx> MaybeResult<typename Ctx::RefTypeT> reftype(Ctx&);
template<typename Ctx> Result<typename Ctx::TypeT> valtype(Ctx&);
template<typename Ctx> MaybeResult<typename Ctx::ParamsT> params(Ctx&);
template<typename Ctx> MaybeResult<typename Ctx::ResultsT> results(Ctx&);
template<typename Ctx> MaybeResult<typename Ctx::SignatureT> functype(Ctx&);
template<typename Ctx> Result<typename Ctx::FieldT> storagetype(Ctx&);
template<typename Ctx> Result<typename Ctx::FieldT> fieldtype(Ctx&);
template<typename Ctx> Result<typename Ctx::FieldsT> fields(Ctx&);
template<typename Ctx> MaybeResult<typename Ctx::StructT> structtype(Ctx&);
template<typename Ctx> MaybeResult<typename Ctx::ArrayT> arraytype(Ctx&);
template<typename Ctx> Result<typename Ctx::LimitsT> limits32(Ctx&);
template<typename Ctx> Result<typename Ctx::LimitsT> limits64(Ctx&);
template<typename Ctx> Result<typename Ctx::MemTypeT> memtype(Ctx&);
template<typename Ctx> Result<typename Ctx::GlobalTypeT> globaltype(Ctx&);
// Instructions
template<typename Ctx> MaybeResult<> foldedBlockinstr(Ctx&);
template<typename Ctx> MaybeResult<> unfoldedBlockinstr(Ctx&);
template<typename Ctx> MaybeResult<> blockinstr(Ctx&);
template<typename Ctx> MaybeResult<> plaininstr(Ctx&);
template<typename Ctx> MaybeResult<> instr(Ctx&);
template<typename Ctx> MaybeResult<> foldedinstr(Ctx&);
template<typename Ctx> Result<> instrs(Ctx&);
template<typename Ctx> Result<> foldedinstrs(Ctx&);
template<typename Ctx> Result<typename Ctx::ExprT> expr(Ctx&);
template<typename Ctx> Result<typename Ctx::MemargT> memarg(Ctx&, uint32_t);
template<typename Ctx> Result<typename Ctx::BlockTypeT> blocktype(Ctx&);
template<typename Ctx> MaybeResult<> block(Ctx&, bool);
template<typename Ctx> MaybeResult<> ifelse(Ctx&, bool);
template<typename Ctx> MaybeResult<> loop(Ctx&, bool);
template<typename Ctx> Result<> makeUnreachable(Ctx&, Index);
template<typename Ctx> Result<> makeNop(Ctx&, Index);
template<typename Ctx> Result<> makeBinary(Ctx&, Index, BinaryOp op);
template<typename Ctx> Result<> makeUnary(Ctx&, Index, UnaryOp op);
template<typename Ctx> Result<> makeSelect(Ctx&, Index);
template<typename Ctx> Result<> makeDrop(Ctx&, Index);
template<typename Ctx> Result<> makeMemorySize(Ctx&, Index);
template<typename Ctx> Result<> makeMemoryGrow(Ctx&, Index);
template<typename Ctx> Result<> makeLocalGet(Ctx&, Index);
template<typename Ctx> Result<> makeLocalTee(Ctx&, Index);
template<typename Ctx> Result<> makeLocalSet(Ctx&, Index);
template<typename Ctx> Result<> makeGlobalGet(Ctx&, Index);
template<typename Ctx> Result<> makeGlobalSet(Ctx&, Index);
template<typename Ctx> Result<> makeBlock(Ctx&, Index);
template<typename Ctx> Result<> makeThenOrElse(Ctx&, Index);
template<typename Ctx> Result<> makeConst(Ctx&, Index, Type type);
template<typename Ctx>
Result<>
makeLoad(Ctx&, Index, Type type, bool signed_, int bytes, bool isAtomic);
template<typename Ctx>
Result<> makeStore(Ctx&, Index, Type type, int bytes, bool isAtomic);
template<typename Ctx>
Result<> makeAtomicRMW(Ctx&, Index, AtomicRMWOp op, Type type, uint8_t bytes);
template<typename Ctx>
Result<> makeAtomicCmpxchg(Ctx&, Index, Type type, uint8_t bytes);
template<typename Ctx> Result<> makeAtomicWait(Ctx&, Index, Type type);
template<typename Ctx> Result<> makeAtomicNotify(Ctx&, Index);
template<typename Ctx> Result<> makeAtomicFence(Ctx&, Index);
template<typename Ctx>
Result<> makeSIMDExtract(Ctx&, Index, SIMDExtractOp op, size_t lanes);
template<typename Ctx>
Result<> makeSIMDReplace(Ctx&, Index, SIMDReplaceOp op, size_t lanes);
template<typename Ctx> Result<> makeSIMDShuffle(Ctx&, Index);
template<typename Ctx> Result<> makeSIMDTernary(Ctx&, Index, SIMDTernaryOp op);
template<typename Ctx> Result<> makeSIMDShift(Ctx&, Index, SIMDShiftOp op);
template<typename Ctx>
Result<> makeSIMDLoad(Ctx&, Index, SIMDLoadOp op, int bytes);
template<typename Ctx>
Result<> makeSIMDLoadStoreLane(Ctx&, Index, SIMDLoadStoreLaneOp op, int bytes);
template<typename Ctx> Result<> makeMemoryInit(Ctx&, Index);
template<typename Ctx> Result<> makeDataDrop(Ctx&, Index);
template<typename Ctx> Result<> makeMemoryCopy(Ctx&, Index);
template<typename Ctx> Result<> makeMemoryFill(Ctx&, Index);
template<typename Ctx> Result<> makePop(Ctx&, Index);
template<typename Ctx> Result<> makeIf(Ctx&, Index);
template<typename Ctx>
Result<> makeMaybeBlock(Ctx&, Index, size_t i, Type type);
template<typename Ctx> Result<> makeLoop(Ctx&, Index);
template<typename Ctx> Result<> makeCall(Ctx&, Index, bool isReturn);
template<typename Ctx> Result<> makeCallIndirect(Ctx&, Index, bool isReturn);
template<typename Ctx> Result<> makeBreak(Ctx&, Index);
template<typename Ctx> Result<> makeBreakTable(Ctx&, Index);
template<typename Ctx> Result<> makeReturn(Ctx&, Index);
template<typename Ctx> Result<> makeRefNull(Ctx&, Index);
template<typename Ctx> Result<> makeRefIsNull(Ctx&, Index);
template<typename Ctx> Result<> makeRefFunc(Ctx&, Index);
template<typename Ctx> Result<> makeRefEq(Ctx&, Index);
template<typename Ctx> Result<> makeTableGet(Ctx&, Index);
template<typename Ctx> Result<> makeTableSet(Ctx&, Index);
template<typename Ctx> Result<> makeTableSize(Ctx&, Index);
template<typename Ctx> Result<> makeTableGrow(Ctx&, Index);
template<typename Ctx> Result<> makeTableFill(Ctx&, Index);
template<typename Ctx> Result<> makeTry(Ctx&, Index);
template<typename Ctx>
Result<> makeTryOrCatchBody(Ctx&, Index, Type type, bool isTry);
template<typename Ctx> Result<> makeThrow(Ctx&, Index);
template<typename Ctx> Result<> makeRethrow(Ctx&, Index);
template<typename Ctx> Result<> makeTupleMake(Ctx&, Index);
template<typename Ctx> Result<> makeTupleExtract(Ctx&, Index);
template<typename Ctx> Result<> makeCallRef(Ctx&, Index, bool isReturn);
template<typename Ctx> Result<> makeRefI31(Ctx&, Index);
template<typename Ctx> Result<> makeI31Get(Ctx&, Index, bool signed_);
template<typename Ctx> Result<> makeRefTest(Ctx&, Index);
template<typename Ctx> Result<> makeRefCast(Ctx&, Index);
template<typename Ctx> Result<> makeBrOnNull(Ctx&, Index, bool onFail = false);
template<typename Ctx> Result<> makeBrOnCast(Ctx&, Index, bool onFail = false);
template<typename Ctx> Result<> makeStructNew(Ctx&, Index, bool default_);
template<typename Ctx>
Result<> makeStructGet(Ctx&, Index, bool signed_ = false);
template<typename Ctx> Result<> makeStructSet(Ctx&, Index);
template<typename Ctx> Result<> makeArrayNew(Ctx&, Index, bool default_);
template<typename Ctx> Result<> makeArrayNewData(Ctx&, Index);
template<typename Ctx> Result<> makeArrayNewElem(Ctx&, Index);
template<typename Ctx> Result<> makeArrayNewFixed(Ctx&, Index);
template<typename Ctx> Result<> makeArrayGet(Ctx&, Index, bool signed_ = false);
template<typename Ctx> Result<> makeArraySet(Ctx&, Index);
template<typename Ctx> Result<> makeArrayLen(Ctx&, Index);
template<typename Ctx> Result<> makeArrayCopy(Ctx&, Index);
template<typename Ctx> Result<> makeArrayFill(Ctx&, Index);
template<typename Ctx> Result<> makeArrayInitData(Ctx&, Index);
template<typename Ctx> Result<> makeArrayInitElem(Ctx&, Index);
template<typename Ctx> Result<> makeRefAs(Ctx&, Index, RefAsOp op);
template<typename Ctx>
Result<> makeStringNew(Ctx&, Index, StringNewOp op, bool try_);
template<typename Ctx> Result<> makeStringConst(Ctx&, Index);
template<typename Ctx>
Result<> makeStringMeasure(Ctx&, Index, StringMeasureOp op);
template<typename Ctx>
Result<> makeStringEncode(Ctx&, Index, StringEncodeOp op);
template<typename Ctx> Result<> makeStringConcat(Ctx&, Index);
template<typename Ctx> Result<> makeStringEq(Ctx&, Index, StringEqOp);
template<typename Ctx> Result<> makeStringAs(Ctx&, Index, StringAsOp op);
template<typename Ctx> Result<> makeStringWTF8Advance(Ctx&, Index);
template<typename Ctx> Result<> makeStringWTF16Get(Ctx&, Index);
template<typename Ctx> Result<> makeStringIterNext(Ctx&, Index);
template<typename Ctx>
Result<> makeStringIterMove(Ctx&, Index, StringIterMoveOp op);
template<typename Ctx>
Result<> makeStringSliceWTF(Ctx&, Index, StringSliceWTFOp op);
template<typename Ctx> Result<> makeStringSliceIter(Ctx&, Index);
// Modules
template<typename Ctx> MaybeResult<Index> maybeTypeidx(Ctx& ctx);
template<typename Ctx> Result<typename Ctx::HeapTypeT> typeidx(Ctx&);
template<typename Ctx>
Result<typename Ctx::FieldIdxT> fieldidx(Ctx&, typename Ctx::HeapTypeT);
template<typename Ctx> MaybeResult<typename Ctx::MemoryIdxT> maybeMemidx(Ctx&);
template<typename Ctx> Result<typename Ctx::MemoryIdxT> memidx(Ctx&);
template<typename Ctx> MaybeResult<typename Ctx::MemoryIdxT> maybeMemuse(Ctx&);
template<typename Ctx> Result<typename Ctx::GlobalIdxT> globalidx(Ctx&);
template<typename Ctx> Result<typename Ctx::LocalIdxT> localidx(Ctx&);
template<typename Ctx> Result<typename Ctx::LabelIdxT> labelidx(Ctx&);
template<typename Ctx> Result<typename Ctx::TypeUseT> typeuse(Ctx&);
MaybeResult<ImportNames> inlineImport(ParseInput&);
Result<std::vector<Name>> inlineExports(ParseInput&);
template<typename Ctx> Result<> strtype(Ctx&);
template<typename Ctx> MaybeResult<typename Ctx::ModuleNameT> subtype(Ctx&);
template<typename Ctx> MaybeResult<> deftype(Ctx&);
template<typename Ctx> MaybeResult<typename Ctx::LocalsT> locals(Ctx&);
template<typename Ctx> MaybeResult<> func(Ctx&);
template<typename Ctx> MaybeResult<> memory(Ctx&);
template<typename Ctx> MaybeResult<> global(Ctx&);
template<typename Ctx> Result<typename Ctx::DataStringT> datastring(Ctx&);
template<typename Ctx> MaybeResult<> data(Ctx&);
template<typename Ctx> MaybeResult<> modulefield(Ctx&);
template<typename Ctx> Result<> module(Ctx&);
// =========
// Utilities
// =========
// RAII utility for temporarily changing the parsing position of a parsing
// context.
template<typename Ctx> struct WithPosition {
Ctx& ctx;
Index original;
WithPosition(Ctx& ctx, Index pos) : ctx(ctx), original(ctx.in.getPos()) {
ctx.in.lexer.setIndex(pos);
}
~WithPosition() { ctx.in.lexer.setIndex(original); }
};
// Deduction guide to satisfy -Wctad-maybe-unsupported.
template<typename Ctx> WithPosition(Ctx& ctx, Index) -> WithPosition<Ctx>;
// =====
// Types
// =====
// heaptype ::= x:typeidx => types[x]
// | 'func' => func
// | 'extern' => extern
template<typename Ctx> Result<typename Ctx::HeapTypeT> heaptype(Ctx& ctx) {
if (ctx.in.takeKeyword("func"sv)) {
return ctx.makeFunc();
}
if (ctx.in.takeKeyword("any"sv)) {
return ctx.makeAny();
}
if (ctx.in.takeKeyword("extern"sv)) {
return ctx.makeExtern();
}
if (ctx.in.takeKeyword("eq"sv)) {
return ctx.makeEq();
}
if (ctx.in.takeKeyword("i31"sv)) {
return ctx.makeI31();
}
if (ctx.in.takeKeyword("struct"sv)) {
return ctx.makeStructType();
}
if (ctx.in.takeKeyword("array"sv)) {
return ctx.makeArrayType();
}
auto type = typeidx(ctx);
CHECK_ERR(type);
return *type;
}
// reftype ::= 'funcref' => funcref
// | 'externref' => externref
// | 'anyref' => anyref
// | 'eqref' => eqref
// | 'i31ref' => i31ref
// | 'structref' => structref
// | 'arrayref' => arrayref
// | '(' ref null? t:heaptype ')' => ref null? t
template<typename Ctx> MaybeResult<typename Ctx::TypeT> reftype(Ctx& ctx) {
if (ctx.in.takeKeyword("funcref"sv)) {
return ctx.makeRefType(ctx.makeFunc(), Nullable);
}
if (ctx.in.takeKeyword("externref"sv)) {
return ctx.makeRefType(ctx.makeExtern(), Nullable);
}
if (ctx.in.takeKeyword("anyref"sv)) {
return ctx.makeRefType(ctx.makeAny(), Nullable);
}
if (ctx.in.takeKeyword("eqref"sv)) {
return ctx.makeRefType(ctx.makeEq(), Nullable);
}
if (ctx.in.takeKeyword("i31ref"sv)) {
return ctx.makeRefType(ctx.makeI31(), Nullable);
}
if (ctx.in.takeKeyword("structref"sv)) {
return ctx.makeRefType(ctx.makeStructType(), Nullable);
}
if (ctx.in.takeKeyword("arrayref"sv)) {
return ctx.in.err("arrayref not yet supported");
}
if (!ctx.in.takeSExprStart("ref"sv)) {
return {};
}
auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable;
auto type = heaptype(ctx);
CHECK_ERR(type);
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of reftype");
}
return ctx.makeRefType(*type, nullability);
}
// 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::TypeT> valtype(Ctx& ctx) {
if (ctx.in.takeKeyword("i32"sv)) {
return ctx.makeI32();
} else if (ctx.in.takeKeyword("i64"sv)) {
return ctx.makeI64();
} else if (ctx.in.takeKeyword("f32"sv)) {
return ctx.makeF32();
} else if (ctx.in.takeKeyword("f64"sv)) {
return ctx.makeF64();
} else if (ctx.in.takeKeyword("v128"sv)) {
return ctx.makeV128();
} else if (auto type = reftype(ctx)) {
CHECK_ERR(type);
return *type;
} else {
return ctx.in.err("expected valtype");
}
}
// param ::= '(' 'param id? t:valtype ')' => [t]
// | '(' 'param t*:valtype* ')' => [t*]
// params ::= param*
template<typename Ctx> MaybeResult<typename Ctx::ParamsT> params(Ctx& ctx) {
bool hasAny = false;
auto res = ctx.makeParams();
while (ctx.in.takeSExprStart("param"sv)) {
hasAny = true;
if (auto id = ctx.in.takeID()) {
// Single named param
auto type = valtype(ctx);
CHECK_ERR(type);
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of param");
}
ctx.appendParam(res, *id, *type);
} else {
// Repeated unnamed params
while (!ctx.in.takeRParen()) {
auto type = valtype(ctx);
CHECK_ERR(type);
ctx.appendParam(res, {}, *type);
}
}
}
if (hasAny) {
return res;
}
return {};
}
// result ::= '(' 'result' t*:valtype ')' => [t*]
// results ::= result*
template<typename Ctx> MaybeResult<typename Ctx::ResultsT> results(Ctx& ctx) {
bool hasAny = false;
auto res = ctx.makeResults();
while (ctx.in.takeSExprStart("result"sv)) {
hasAny = true;
while (!ctx.in.takeRParen()) {
auto type = valtype(ctx);
CHECK_ERR(type);
ctx.appendResult(res, *type);
}
}
if (hasAny) {
return res;
}
return {};
}
// functype ::= '(' 'func' t1*:vec(param) t2*:vec(result) ')' => [t1*] -> [t2*]
template<typename Ctx>
MaybeResult<typename Ctx::SignatureT> functype(Ctx& ctx) {
if (!ctx.in.takeSExprStart("func"sv)) {
return {};
}
auto parsedParams = params(ctx);
CHECK_ERR(parsedParams);
auto parsedResults = results(ctx);
CHECK_ERR(parsedResults);
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of functype");
}
return ctx.makeFuncType(parsedParams.getPtr(), parsedResults.getPtr());
}
// storagetype ::= valtype | packedtype
// packedtype ::= i8 | i16
template<typename Ctx> Result<typename Ctx::FieldT> storagetype(Ctx& ctx) {
if (ctx.in.takeKeyword("i8"sv)) {
return ctx.makeI8();
}
if (ctx.in.takeKeyword("i16"sv)) {
return ctx.makeI16();
}
auto type = valtype(ctx);
CHECK_ERR(type);
return ctx.makeStorageType(*type);
}
// fieldtype ::= t:storagetype => const t
// | '(' 'mut' t:storagetype ')' => var t
template<typename Ctx> Result<typename Ctx::FieldT> fieldtype(Ctx& ctx) {
auto mutability = Immutable;
if (ctx.in.takeSExprStart("mut"sv)) {
mutability = Mutable;
}
auto field = storagetype(ctx);
CHECK_ERR(field);
if (mutability == Mutable) {
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of field type");
}
}
return ctx.makeFieldType(*field, mutability);
}
// field ::= '(' 'field' id t:fieldtype ')' => [(id, t)]
// | '(' 'field' t*:fieldtype* ')' => [(_, t*)*]
// | fieldtype
template<typename Ctx> Result<typename Ctx::FieldsT> fields(Ctx& ctx) {
auto res = ctx.makeFields();
while (true) {
if (auto t = ctx.in.peek(); !t || t->isRParen()) {
return res;
}
if (ctx.in.takeSExprStart("field")) {
if (auto id = ctx.in.takeID()) {
auto field = fieldtype(ctx);
CHECK_ERR(field);
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of field");
}
ctx.appendField(res, *id, *field);
} else {
while (!ctx.in.takeRParen()) {
auto field = fieldtype(ctx);
CHECK_ERR(field);
ctx.appendField(res, {}, *field);
}
}
} else {
auto field = fieldtype(ctx);
CHECK_ERR(field);
ctx.appendField(res, {}, *field);
}
}
}
// structtype ::= '(' 'struct' field* ')'
template<typename Ctx> MaybeResult<typename Ctx::StructT> structtype(Ctx& ctx) {
if (!ctx.in.takeSExprStart("struct"sv)) {
return {};
}
auto namedFields = fields(ctx);
CHECK_ERR(namedFields);
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of struct definition");
}
return ctx.makeStruct(*namedFields);
}
// arraytype ::= '(' 'array' field ')'
template<typename Ctx> MaybeResult<typename Ctx::ArrayT> arraytype(Ctx& ctx) {
if (!ctx.in.takeSExprStart("array"sv)) {
return {};
}
auto namedFields = fields(ctx);
CHECK_ERR(namedFields);
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of array definition");
}
if (auto array = ctx.makeArray(*namedFields)) {
return *array;
}
return ctx.in.err("expected exactly one field in array definition");
}
// limits32 ::= n:u32 m:u32?
template<typename Ctx> Result<typename Ctx::LimitsT> limits32(Ctx& ctx) {
auto n = ctx.in.takeU32();
if (!n) {
return ctx.in.err("expected initial size");
}
std::optional<uint64_t> m = ctx.in.takeU32();
return ctx.makeLimits(uint64_t(*n), m);
}
// limits64 ::= n:u64 m:u64?
template<typename Ctx> Result<typename Ctx::LimitsT> limits64(Ctx& ctx) {
auto n = ctx.in.takeU64();
if (!n) {
return ctx.in.err("expected initial size");
}
std::optional<uint64_t> m = ctx.in.takeU64();
return ctx.makeLimits(uint64_t(*n), m);
}
// memtype ::= (limits32 | 'i32' limits32 | 'i64' limit64) shared?
template<typename Ctx> Result<typename Ctx::MemTypeT> memtype(Ctx& ctx) {
auto type = Type::i32;
if (ctx.in.takeKeyword("i64"sv)) {
type = Type::i64;
} else {
ctx.in.takeKeyword("i32"sv);
}
auto limits = type == Type::i32 ? limits32(ctx) : limits64(ctx);
CHECK_ERR(limits);
bool shared = false;
if (ctx.in.takeKeyword("shared"sv)) {
shared = true;
}
return ctx.makeMemType(type, *limits, shared);
}
// globaltype ::= t:valtype => const t
// | '(' 'mut' t:valtype ')' => var t
template<typename Ctx> Result<typename Ctx::GlobalTypeT> globaltype(Ctx& ctx) {
auto mutability = Immutable;
if (ctx.in.takeSExprStart("mut"sv)) {
mutability = Mutable;
}
auto type = valtype(ctx);
CHECK_ERR(type);
if (mutability == Mutable && !ctx.in.takeRParen()) {
return ctx.in.err("expected end of globaltype");
}
return ctx.makeGlobalType(mutability, *type);
}
// ============
// Instructions
// ============
// blockinstr ::= block | loop | if-else | try-catch
template<typename Ctx> MaybeResult<> foldedBlockinstr(Ctx& ctx) {
if (auto i = block(ctx, true)) {
return i;
}
if (auto i = ifelse(ctx, true)) {
return i;
}
if (auto i = loop(ctx, true)) {
return i;
}
// TODO: Other block instructions
return {};
}
template<typename Ctx> MaybeResult<> unfoldedBlockinstr(Ctx& ctx) {
if (auto i = block(ctx, false)) {
return i;
}
if (auto i = ifelse(ctx, false)) {
return i;
}
if (auto i = loop(ctx, false)) {
return i;
}
// TODO: Other block instructions
return {};
}
template<typename Ctx> MaybeResult<> blockinstr(Ctx& ctx) {
if (auto i = foldedBlockinstr(ctx)) {
return i;
}
if (auto i = unfoldedBlockinstr(ctx)) {
return i;
}
return {};
}
// plaininstr ::= ... all plain instructions ...
template<typename Ctx> MaybeResult<> plaininstr(Ctx& ctx) {
auto pos = ctx.in.getPos();
auto keyword = ctx.in.takeKeyword();
if (!keyword) {
return {};
}
#define NEW_INSTRUCTION_PARSER
#define NEW_WAT_PARSER
#include <gen-s-parser.inc>
}
// instr ::= plaininstr | blockinstr
template<typename Ctx> MaybeResult<> instr(Ctx& ctx) {
// Check for valid strings that are not instructions.
if (auto tok = ctx.in.peek()) {
if (auto keyword = tok->getKeyword()) {
if (keyword == "end"sv || keyword == "then"sv || keyword == "else"sv) {
return {};
}
}
}
if (auto inst = blockinstr(ctx)) {
return inst;
}
if (auto inst = plaininstr(ctx)) {
return inst;
}
// TODO: Handle folded plain instructions as well.
return {};
}
template<typename Ctx> MaybeResult<> foldedinstr(Ctx& ctx) {
// Check for valid strings that are not instructions.
if (ctx.in.peekSExprStart("then"sv) || ctx.in.peekSExprStart("else")) {
return {};
}
if (auto inst = foldedBlockinstr(ctx)) {
return inst;
}
if (!ctx.in.takeLParen()) {
return {};
}
// A stack of (start, end) position pairs defining the positions of
// instructions that need to be parsed after their folded children.
std::vector<std::pair<Index, std::optional<Index>>> foldedInstrs;
// Begin a folded instruction. Push its start position and a placeholder
// end position.
foldedInstrs.push_back({ctx.in.getPos(), {}});
while (!foldedInstrs.empty()) {
// Consume everything up to the next paren. This span will be parsed as
// an instruction later after its folded children have been parsed.
if (!ctx.in.takeUntilParen()) {
return ctx.in.err(foldedInstrs.back().first,
"unterminated folded instruction");
}
if (!foldedInstrs.back().second) {
// The folded instruction we just started should end here.
foldedInstrs.back().second = ctx.in.getPos();
}
// We have either the start of a new folded child or the end of the last
// one.
if (auto blockinst = foldedBlockinstr(ctx)) {
CHECK_ERR(blockinst);
} else if (ctx.in.takeLParen()) {
foldedInstrs.push_back({ctx.in.getPos(), {}});
} else if (ctx.in.takeRParen()) {
auto [start, end] = foldedInstrs.back();
assert(end && "Should have found end of instruction");
foldedInstrs.pop_back();
WithPosition with(ctx, start);
if (auto inst = plaininstr(ctx)) {
CHECK_ERR(inst);
} else {
return ctx.in.err(start, "expected folded instruction");
}
if (ctx.in.getPos() != *end) {
return ctx.in.err("expected end of instruction");
}
} else {
WASM_UNREACHABLE("expected paren");
}
}
return Ok{};
}
template<typename Ctx> Result<> instrs(Ctx& ctx) {
while (true) {
if (auto inst = instr(ctx)) {
CHECK_ERR(inst);
continue;
}
if (auto inst = foldedinstr(ctx)) {
CHECK_ERR(inst);
continue;
}
break;
}
return Ok{};
}
template<typename Ctx> Result<> foldedinstrs(Ctx& ctx) {
while (auto inst = foldedinstr(ctx)) {
CHECK_ERR(inst);
}
return Ok{};
}
template<typename Ctx> Result<typename Ctx::ExprT> expr(Ctx& ctx) {
CHECK_ERR(instrs(ctx));
return ctx.makeExpr();
}
// memarg_n ::= o:offset a:align_n
// offset ::= 'offset='o:u64 => o | _ => 0
// align_n ::= 'align='a:u32 => a | _ => n
template<typename Ctx>
Result<typename Ctx::MemargT> memarg(Ctx& ctx, uint32_t n) {
uint64_t offset = 0;
uint32_t align = n;
if (auto o = ctx.in.takeOffset()) {
offset = *o;
}
if (auto a = ctx.in.takeAlign()) {
align = *a;
}
return ctx.getMemarg(offset, align);
}
// blocktype ::= (t:result)? => t? | x,I:typeuse => x if I = {}
template<typename Ctx> Result<typename Ctx::BlockTypeT> blocktype(Ctx& ctx) {
auto pos = ctx.in.getPos();
if (auto res = results(ctx)) {
CHECK_ERR(res);
if (ctx.getResultsSize(*res) == 1) {
return ctx.getBlockTypeFromResult(*res);
}
}
// We either had no results or multiple results. Reset and parse again as a
// type use.
ctx.in.lexer.setIndex(pos);
auto use = typeuse(ctx);
CHECK_ERR(use);
auto type = ctx.getBlockTypeFromTypeUse(pos, *use);
CHECK_ERR(type);
return *type;
}
// block ::= 'block' label blocktype instr* 'end' id? if id = {} or id = label
// | '(' 'block' label blocktype instr* ')'
template<typename Ctx> MaybeResult<> block(Ctx& ctx, bool folded) {
auto pos = ctx.in.getPos();
if ((folded && !ctx.in.takeSExprStart("block"sv)) ||
(!folded && !ctx.in.takeKeyword("block"sv))) {
return {};
}
auto label = ctx.in.takeID();
auto type = blocktype(ctx);
CHECK_ERR(type);
ctx.makeBlock(pos, label, *type);
CHECK_ERR(instrs(ctx));
if (folded) {
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected ')' at end of block");
}
} else {
if (!ctx.in.takeKeyword("end"sv)) {
return ctx.in.err("expected 'end' at end of block");
}
auto id = ctx.in.takeID();
if (id && id != label) {
return ctx.in.err("end label does not match block label");
}
}
return ctx.visitEnd();
}
// if ::= 'if' label blocktype instr1* ('else' id1? instr2*)? 'end' id2?
// | '(' 'if' label blocktype foldedinstr* '(' 'then' instr1* ')'
// ('(' 'else' instr2* ')')? ')'
template<typename Ctx> MaybeResult<> ifelse(Ctx& ctx, bool folded) {
auto pos = ctx.in.getPos();
if ((folded && !ctx.in.takeSExprStart("if"sv)) ||
(!folded && !ctx.in.takeKeyword("if"sv))) {
return {};
}
auto label = ctx.in.takeID();
auto type = blocktype(ctx);
CHECK_ERR(type);
if (folded) {
CHECK_ERR(foldedinstrs(ctx));
}
ctx.makeIf(pos, label, *type);
if (folded && !ctx.in.takeSExprStart("then"sv)) {
return ctx.in.err("expected 'then' before if instructions");
}
CHECK_ERR(instrs(ctx));
if (folded && !ctx.in.takeRParen()) {
return ctx.in.err("expected ')' at end of then block");
}
if ((folded && ctx.in.takeSExprStart("else"sv)) ||
(!folded && ctx.in.takeKeyword("else"sv))) {
auto id1 = ctx.in.takeID();
if (id1 && id1 != label) {
return ctx.in.err("else label does not match if label");
}
ctx.visitElse();
CHECK_ERR(instrs(ctx));
if (folded && !ctx.in.takeRParen()) {
return ctx.in.err("expected ')' at end of else block");
}
}
if (folded) {
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected ')' at end of if");
}
} else {
if (!ctx.in.takeKeyword("end"sv)) {
return ctx.in.err("expected 'end' at end of if");
}
auto id2 = ctx.in.takeID();
if (id2 && id2 != label) {
return ctx.in.err("end label does not match if label");
}
}
return ctx.visitEnd();
}
// loop ::= 'loop' label blocktype instr* end id?
// | '(' 'loop' label blocktype instr* ')'
template<typename Ctx> MaybeResult<> loop(Ctx& ctx, bool folded) {
auto pos = ctx.in.getPos();
if ((folded && !ctx.in.takeSExprStart("loop"sv)) ||
(!folded && !ctx.in.takeKeyword("loop"sv))) {
return {};
}
auto label = ctx.in.takeID();
auto type = blocktype(ctx);
CHECK_ERR(type);
ctx.makeLoop(pos, label, *type);
CHECK_ERR(instrs(ctx));
if (folded) {
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected ')' at end of loop");
}
} else {
if (!ctx.in.takeKeyword("end"sv)) {
return ctx.in.err("expected 'end' at end of loop");
}
auto id = ctx.in.takeID();
if (id && id != label) {
return ctx.in.err("end label does not match loop label");
}
}
return ctx.visitEnd();
}
template<typename Ctx> Result<> makeUnreachable(Ctx& ctx, Index pos) {
return ctx.makeUnreachable(pos);
}
template<typename Ctx> Result<> makeNop(Ctx& ctx, Index pos) {
return ctx.makeNop(pos);
}
template<typename Ctx> Result<> makeBinary(Ctx& ctx, Index pos, BinaryOp op) {
return ctx.makeBinary(pos, op);
}
template<typename Ctx> Result<> makeUnary(Ctx& ctx, Index pos, UnaryOp op) {
return ctx.makeUnary(pos, op);
}
template<typename Ctx> Result<> makeSelect(Ctx& ctx, Index pos) {
auto res = results(ctx);
CHECK_ERR(res);
return ctx.makeSelect(pos, res.getPtr());
}
template<typename Ctx> Result<> makeDrop(Ctx& ctx, Index pos) {
return ctx.makeDrop(pos);
}
template<typename Ctx> Result<> makeMemorySize(Ctx& ctx, Index pos) {
auto mem = maybeMemidx(ctx);
CHECK_ERR(mem);
return ctx.makeMemorySize(pos, mem.getPtr());
}
template<typename Ctx> Result<> makeMemoryGrow(Ctx& ctx, Index pos) {
auto mem = maybeMemidx(ctx);
CHECK_ERR(mem);
return ctx.makeMemoryGrow(pos, mem.getPtr());
}
template<typename Ctx> Result<> makeLocalGet(Ctx& ctx, Index pos) {
auto local = localidx(ctx);
CHECK_ERR(local);
return ctx.makeLocalGet(pos, *local);
}
template<typename Ctx> Result<> makeLocalTee(Ctx& ctx, Index pos) {
auto local = localidx(ctx);
CHECK_ERR(local);
return ctx.makeLocalTee(pos, *local);
}
template<typename Ctx> Result<> makeLocalSet(Ctx& ctx, Index pos) {
auto local = localidx(ctx);
CHECK_ERR(local);
return ctx.makeLocalSet(pos, *local);
}
template<typename Ctx> Result<> makeGlobalGet(Ctx& ctx, Index pos) {
auto global = globalidx(ctx);
CHECK_ERR(global);
return ctx.makeGlobalGet(pos, *global);
}
template<typename Ctx> Result<> makeGlobalSet(Ctx& ctx, Index pos) {
auto global = globalidx(ctx);
CHECK_ERR(global);
return ctx.makeGlobalSet(pos, *global);
}
template<typename Ctx> Result<> makeBlock(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeConst(Ctx& ctx, Index pos, Type type) {
assert(type.isBasic());
switch (type.getBasic()) {
case Type::i32:
if (auto c = ctx.in.takeI32()) {
return ctx.makeI32Const(pos, *c);
}
return ctx.in.err("expected i32");
case Type::i64:
if (auto c = ctx.in.takeI64()) {
return ctx.makeI64Const(pos, *c);
}
return ctx.in.err("expected i64");
case Type::f32:
if (auto c = ctx.in.takeF32()) {
return ctx.makeF32Const(pos, *c);
}
return ctx.in.err("expected f32");
case Type::f64:
if (auto c = ctx.in.takeF64()) {
return ctx.makeF64Const(pos, *c);
}
return ctx.in.err("expected f64");
case Type::v128:
return ctx.in.err("unimplemented instruction");
case Type::none:
case Type::unreachable:
break;
}
WASM_UNREACHABLE("unexpected type");
}
template<typename Ctx>
Result<> makeLoad(
Ctx& ctx, Index pos, Type type, bool signed_, int bytes, bool isAtomic) {
auto mem = maybeMemidx(ctx);
CHECK_ERR(mem);
auto arg = memarg(ctx, bytes);
CHECK_ERR(arg);
return ctx.makeLoad(pos, type, signed_, bytes, isAtomic, mem.getPtr(), *arg);
}
template<typename Ctx>
Result<> makeStore(Ctx& ctx, Index pos, Type type, int bytes, bool isAtomic) {
auto mem = maybeMemidx(ctx);
CHECK_ERR(mem);
auto arg = memarg(ctx, bytes);
CHECK_ERR(arg);
return ctx.makeStore(pos, type, bytes, isAtomic, mem.getPtr(), *arg);
}
template<typename Ctx>
Result<>
makeAtomicRMW(Ctx& ctx, Index pos, AtomicRMWOp op, Type type, uint8_t bytes) {
auto mem = maybeMemidx(ctx);
CHECK_ERR(mem);
auto arg = memarg(ctx, bytes);
CHECK_ERR(arg);
return ctx.makeAtomicRMW(pos, op, type, bytes, mem.getPtr(), *arg);
}
template<typename Ctx>
Result<> makeAtomicCmpxchg(Ctx& ctx, Index pos, Type type, uint8_t bytes) {
auto mem = maybeMemidx(ctx);
CHECK_ERR(mem);
auto arg = memarg(ctx, bytes);
CHECK_ERR(arg);
return ctx.makeAtomicCmpxchg(pos, type, bytes, mem.getPtr(), *arg);
}
template<typename Ctx> Result<> makeAtomicWait(Ctx& ctx, Index pos, Type type) {
auto mem = maybeMemidx(ctx);
CHECK_ERR(mem);
auto arg = memarg(ctx, type == Type::i32 ? 4 : 8);
CHECK_ERR(arg);
return ctx.makeAtomicWait(pos, type, mem.getPtr(), *arg);
}
template<typename Ctx> Result<> makeAtomicNotify(Ctx& ctx, Index pos) {
auto mem = maybeMemidx(ctx);
CHECK_ERR(mem);
auto arg = memarg(ctx, 4);
CHECK_ERR(arg);
return ctx.makeAtomicNotify(pos, mem.getPtr(), *arg);
}
template<typename Ctx> Result<> makeAtomicFence(Ctx& ctx, Index pos) {
return ctx.makeAtomicFence(pos);
}
template<typename Ctx>
Result<> makeSIMDExtract(Ctx& ctx, Index pos, SIMDExtractOp op, size_t) {
auto lane = ctx.in.takeU8();
if (!lane) {
return ctx.in.err("expected lane index");
}
return ctx.makeSIMDExtract(pos, op, *lane);
}
template<typename Ctx>
Result<> makeSIMDReplace(Ctx& ctx, Index pos, SIMDReplaceOp op, size_t lanes) {
auto lane = ctx.in.takeU8();
if (!lane) {
return ctx.in.err("expected lane index");
}
return ctx.makeSIMDReplace(pos, op, *lane);
}
template<typename Ctx> Result<> makeSIMDShuffle(Ctx& ctx, Index pos) {
std::array<uint8_t, 16> lanes;
for (int i = 0; i < 16; ++i) {
auto lane = ctx.in.takeU8();
if (!lane) {
return ctx.in.err("expected lane index");
}
lanes[i] = *lane;
}
return ctx.makeSIMDShuffle(pos, lanes);
}
template<typename Ctx>
Result<> makeSIMDTernary(Ctx& ctx, Index pos, SIMDTernaryOp op) {
return ctx.makeSIMDTernary(pos, op);
}
template<typename Ctx>
Result<> makeSIMDShift(Ctx& ctx, Index pos, SIMDShiftOp op) {
return ctx.makeSIMDShift(pos, op);
}
template<typename Ctx>
Result<> makeSIMDLoad(Ctx& ctx, Index pos, SIMDLoadOp op, int bytes) {
auto mem = maybeMemidx(ctx);
CHECK_ERR(mem);
auto arg = memarg(ctx, bytes);
CHECK_ERR(arg);
return ctx.makeSIMDLoad(pos, op, mem.getPtr(), *arg);
}
template<typename Ctx>
Result<>
makeSIMDLoadStoreLane(Ctx& ctx, Index pos, SIMDLoadStoreLaneOp op, int bytes) {
auto reset = ctx.in.getPos();
auto retry = [&]() -> Result<> {
// We failed to parse. Maybe the lane index was accidentally parsed as the
// optional memory index. Try again without parsing a memory index.
WithPosition with(ctx, reset);
auto arg = memarg(ctx, bytes);
CHECK_ERR(arg);
auto lane = ctx.in.takeU8();
if (!lane) {
return ctx.in.err("expected lane index");
}
return ctx.makeSIMDLoadStoreLane(pos, op, nullptr, *arg, *lane);
};
auto mem = maybeMemidx(ctx);
if (mem.getErr()) {
return retry();
}
auto arg = memarg(ctx, bytes);
CHECK_ERR(arg);
auto lane = ctx.in.takeU8();
if (!lane) {
return retry();
}
return ctx.makeSIMDLoadStoreLane(pos, op, mem.getPtr(), *arg, *lane);
}
template<typename Ctx> Result<> makeMemoryInit(Ctx& ctx, Index pos) {
auto reset = ctx.in.getPos();
auto retry = [&]() -> Result<> {
// We failed to parse. Maybe the data index was accidentally parsed as the
// optional memory index. Try again without parsing a memory index.
WithPosition with(ctx, reset);
auto data = dataidx(ctx);
CHECK_ERR(data);
return ctx.makeMemoryInit(pos, nullptr, *data);
};
auto mem = maybeMemidx(ctx);
if (mem.getErr()) {
return retry();
}
auto data = dataidx(ctx);
if (data.getErr()) {
return retry();
}
return ctx.makeMemoryInit(pos, mem.getPtr(), *data);
}
template<typename Ctx> Result<> makeDataDrop(Ctx& ctx, Index pos) {
auto data = dataidx(ctx);
CHECK_ERR(data);
return ctx.makeDataDrop(pos, *data);
}
template<typename Ctx> Result<> makeMemoryCopy(Ctx& ctx, Index pos) {
auto destMem = maybeMemidx(ctx);
CHECK_ERR(destMem);
std::optional<typename Ctx::MemoryIdxT> srcMem = std::nullopt;
if (destMem) {
auto mem = memidx(ctx);
CHECK_ERR(mem);
srcMem = *mem;
}
return ctx.makeMemoryCopy(pos, destMem.getPtr(), srcMem ? &*srcMem : nullptr);
}
template<typename Ctx> Result<> makeMemoryFill(Ctx& ctx, Index pos) {
auto mem = maybeMemidx(ctx);
CHECK_ERR(mem);
return ctx.makeMemoryFill(pos, mem.getPtr());
}
template<typename Ctx> Result<> makePop(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeIf(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<> makeMaybeBlock(Ctx& ctx, Index pos, size_t i, Type type) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeLoop(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeCall(Ctx& ctx, Index pos, bool isReturn) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<> makeCallIndirect(Ctx& ctx, Index pos, bool isReturn) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeBreak(Ctx& ctx, Index pos) {
auto label = labelidx(ctx);
CHECK_ERR(label);
return ctx.makeBreak(pos, *label);
}
template<typename Ctx> Result<> makeBreakTable(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeReturn(Ctx& ctx, Index pos) {
return ctx.makeReturn(pos);
}
template<typename Ctx> Result<> makeRefNull(Ctx& ctx, Index pos) {
auto t = heaptype(ctx);
CHECK_ERR(t);
return ctx.makeRefNull(pos, *t);
}
template<typename Ctx> Result<> makeRefIsNull(Ctx& ctx, Index pos) {
return ctx.makeRefIsNull(pos);
}
template<typename Ctx> Result<> makeRefFunc(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeRefEq(Ctx& ctx, Index pos) {
return ctx.makeRefEq(pos);
}
template<typename Ctx> Result<> makeTableGet(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeTableSet(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeTableSize(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeTableGrow(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeTableFill(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeTry(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<> makeTryOrCatchBody(Ctx& ctx, Index pos, Type type, bool isTry) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeThrow(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeRethrow(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeTupleMake(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeTupleExtract(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<> makeCallRef(Ctx& ctx, Index pos, bool isReturn) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeRefI31(Ctx& ctx, Index pos) {
return ctx.makeRefI31(pos);
}
template<typename Ctx> Result<> makeI31Get(Ctx& ctx, Index pos, bool signed_) {
return ctx.makeI31Get(pos, signed_);
}
template<typename Ctx> Result<> makeRefTest(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeRefCast(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeBrOnNull(Ctx& ctx, Index pos, bool onFail) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeBrOnCast(Ctx& ctx, Index pos, bool onFail) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<> makeStructNew(Ctx& ctx, Index pos, bool default_) {
auto type = typeidx(ctx);
CHECK_ERR(type);
if (default_) {
return ctx.makeStructNewDefault(pos, *type);
}
return ctx.makeStructNew(pos, *type);
}
template<typename Ctx>
Result<> makeStructGet(Ctx& ctx, Index pos, bool signed_) {
auto type = typeidx(ctx);
CHECK_ERR(type);
auto field = fieldidx(ctx, *type);
CHECK_ERR(field);
return ctx.makeStructGet(pos, *type, *field, signed_);
}
template<typename Ctx> Result<> makeStructSet(Ctx& ctx, Index pos) {
auto type = typeidx(ctx);
CHECK_ERR(type);
auto field = fieldidx(ctx, *type);
CHECK_ERR(field);
return ctx.makeStructSet(pos, *type, *field);
}
template<typename Ctx>
Result<> makeArrayNew(Ctx& ctx, Index pos, bool default_) {
auto type = typeidx(ctx);
CHECK_ERR(type);
if (default_) {
return ctx.makeArrayNewDefault(pos, *type);
}
return ctx.makeArrayNew(pos, *type);
}
template<typename Ctx> Result<> makeArrayNewData(Ctx& ctx, Index pos) {
auto type = typeidx(ctx);
CHECK_ERR(type);
auto data = dataidx(ctx);
CHECK_ERR(data);
return ctx.makeArrayNewData(pos, *type, *data);
}
template<typename Ctx> Result<> makeArrayNewElem(Ctx& ctx, Index pos) {
auto type = typeidx(ctx);
CHECK_ERR(type);
auto data = dataidx(ctx);
CHECK_ERR(data);
return ctx.makeArrayNewElem(pos, *type, *data);
}
template<typename Ctx> Result<> makeArrayNewFixed(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<> makeArrayGet(Ctx& ctx, Index pos, bool signed_) {
auto type = typeidx(ctx);
CHECK_ERR(type);
return ctx.makeArrayGet(pos, *type, signed_);
}
template<typename Ctx> Result<> makeArraySet(Ctx& ctx, Index pos) {
auto type = typeidx(ctx);
CHECK_ERR(type);
return ctx.makeArraySet(pos, *type);
}
template<typename Ctx> Result<> makeArrayLen(Ctx& ctx, Index pos) {
return ctx.makeArrayLen(pos);
}
template<typename Ctx> Result<> makeArrayCopy(Ctx& ctx, Index pos) {
auto destType = typeidx(ctx);
CHECK_ERR(destType);
auto srcType = typeidx(ctx);
CHECK_ERR(srcType);
return ctx.makeArrayCopy(pos, *destType, *srcType);
}
template<typename Ctx> Result<> makeArrayFill(Ctx& ctx, Index pos) {
auto type = typeidx(ctx);
CHECK_ERR(type);
return ctx.makeArrayFill(pos, *type);
}
template<typename Ctx> Result<> makeArrayInitData(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeArrayInitElem(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeRefAs(Ctx& ctx, Index pos, RefAsOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<> makeStringNew(Ctx& ctx, Index pos, StringNewOp op, bool try_) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeStringConst(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<> makeStringMeasure(Ctx& ctx, Index pos, StringMeasureOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<> makeStringEncode(Ctx& ctx, Index pos, StringEncodeOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeStringConcat(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<> makeStringEq(Ctx& ctx, Index pos, StringEqOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<> makeStringAs(Ctx& ctx, Index pos, StringAsOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeStringWTF8Advance(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeStringWTF16Get(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeStringIterNext(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<> makeStringIterMove(Ctx& ctx, Index pos, StringIterMoveOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<> makeStringSliceWTF(Ctx& ctx, Index pos, StringSliceWTFOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx> Result<> makeStringSliceIter(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
// =======
// Modules
// =======
// typeidx ::= x:u32 => x
// | v:id => x (if types[x] = v)
template<typename Ctx> MaybeResult<Index> maybeTypeidx(Ctx& ctx) {
if (auto x = ctx.in.takeU32()) {
return *x;
}
if (auto id = ctx.in.takeID()) {
// TODO: Fix position to point to start of id, not next element.
auto idx = ctx.getTypeIndex(*id);
CHECK_ERR(idx);
return *idx;
}
return {};
}
template<typename Ctx> Result<typename Ctx::HeapTypeT> typeidx(Ctx& ctx) {
if (auto idx = maybeTypeidx(ctx)) {
CHECK_ERR(idx);
return ctx.getHeapTypeFromIdx(*idx);
}
return ctx.in.err("expected type index or identifier");
}
// fieldidx_t ::= x:u32 => x
// | v:id => x (if t.fields[x] = v)
template<typename Ctx>
Result<typename Ctx::FieldIdxT> fieldidx(Ctx& ctx,
typename Ctx::HeapTypeT type) {
if (auto x = ctx.in.takeU32()) {
return ctx.getFieldFromIdx(type, *x);
}
if (auto id = ctx.in.takeID()) {
return ctx.getFieldFromName(type, *id);
}
return ctx.in.err("expected field index or identifier");
}
// memidx ::= x:u32 => x
// | v:id => x (if memories[x] = v)
template<typename Ctx>
MaybeResult<typename Ctx::MemoryIdxT> maybeMemidx(Ctx& ctx) {
if (auto x = ctx.in.takeU32()) {
return ctx.getMemoryFromIdx(*x);
}
if (auto id = ctx.in.takeID()) {
return ctx.getMemoryFromName(*id);
}
return {};
}
template<typename Ctx> Result<typename Ctx::MemoryIdxT> memidx(Ctx& ctx) {
if (auto idx = maybeMemidx(ctx)) {
CHECK_ERR(idx);
return *idx;
}
return ctx.in.err("expected memory index or identifier");
}
// memuse ::= '(' 'memory' x:memidx ')' => x
template<typename Ctx>
MaybeResult<typename Ctx::MemoryIdxT> maybeMemuse(Ctx& ctx) {
if (!ctx.in.takeSExprStart("memory"sv)) {
return {};
}
auto idx = memidx(ctx);
CHECK_ERR(idx);
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of memory use");
}
return *idx;
}
// globalidx ::= x:u32 => x
// | v:id => x (if globals[x] = v)
template<typename Ctx> Result<typename Ctx::GlobalIdxT> globalidx(Ctx& ctx) {
if (auto x = ctx.in.takeU32()) {
return ctx.getGlobalFromIdx(*x);
}
if (auto id = ctx.in.takeID()) {
return ctx.getGlobalFromName(*id);
}
return ctx.in.err("expected global index or identifier");
}
// dataidx ::= x:u32 => x
// | v:id => x (if data[x] = v)
template<typename Ctx> Result<typename Ctx::DataIdxT> dataidx(Ctx& ctx) {
if (auto x = ctx.in.takeU32()) {
return ctx.getDataFromIdx(*x);
}
if (auto id = ctx.in.takeID()) {
return ctx.getDataFromName(*id);
}
return ctx.in.err("expected data index or identifier");
}
// localidx ::= x:u32 => x
// | v:id => x (if locals[x] = v)
template<typename Ctx> Result<typename Ctx::LocalIdxT> localidx(Ctx& ctx) {
if (auto x = ctx.in.takeU32()) {
return ctx.getLocalFromIdx(*x);
}
if (auto id = ctx.in.takeID()) {
return ctx.getLocalFromName(*id);
}
return ctx.in.err("expected local index or identifier");
}
// labelidx ::= x:u32 => x
// | v:id => x (if labels[x] = v)
template<typename Ctx> Result<typename Ctx::LabelIdxT> labelidx(Ctx& ctx) {
if (auto x = ctx.in.takeU32()) {
return ctx.getLabelFromIdx(*x);
}
if (auto id = ctx.in.takeID()) {
return ctx.getLabelFromName(*id);
}
return ctx.in.err("expected label index or identifier");
}
// typeuse ::= '(' 'type' x:typeidx ')' => x, []
// (if typedefs[x] = [t1*] -> [t2*]
// | '(' 'type' x:typeidx ')' ((t1,IDs):param)* (t2:result)* => x, IDs
// (if typedefs[x] = [t1*] -> [t2*])
// | ((t1,IDs):param)* (t2:result)* => x, IDs
// (if x is minimum s.t. typedefs[x] = [t1*] -> [t2*])
template<typename Ctx> Result<typename Ctx::TypeUseT> typeuse(Ctx& ctx) {
auto pos = ctx.in.getPos();
std::optional<typename Ctx::HeapTypeT> type;
if (ctx.in.takeSExprStart("type"sv)) {
auto x = typeidx(ctx);
CHECK_ERR(x);
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of type use");
}
type = *x;
}
auto namedParams = params(ctx);
CHECK_ERR(namedParams);
auto resultTypes = results(ctx);
CHECK_ERR(resultTypes);
return ctx.makeTypeUse(pos, type, namedParams.getPtr(), resultTypes.getPtr());
}
// ('(' '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");
}
// TODO: Return Ok when parsing Decls.
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;
}
// strtype ::= ft:functype => ft
// | st:structtype => st
// | at:arraytype => at
template<typename Ctx> Result<> strtype(Ctx& ctx) {
if (auto type = functype(ctx)) {
CHECK_ERR(type);
ctx.addFuncType(*type);
return Ok{};
}
if (auto type = structtype(ctx)) {
CHECK_ERR(type);
ctx.addStructType(*type);
return Ok{};
}
if (auto type = arraytype(ctx)) {
CHECK_ERR(type);
ctx.addArrayType(*type);
return Ok{};
}
return ctx.in.err("expected type description");
}
// subtype ::= '(' 'type' id? '(' 'sub' typeidx? strtype ')' ')'
// | '(' 'type' id? strtype ')'
template<typename Ctx> MaybeResult<> subtype(Ctx& ctx) {
auto pos = ctx.in.getPos();
if (!ctx.in.takeSExprStart("type"sv)) {
return {};
}
Name name;
if (auto id = ctx.in.takeID()) {
name = *id;
}
if (ctx.in.takeSExprStart("sub"sv)) {
if (ctx.in.takeKeyword("open"sv)) {
ctx.setOpen();
}
if (auto super = maybeTypeidx(ctx)) {
CHECK_ERR(super);
CHECK_ERR(ctx.addSubtype(*super));
}
CHECK_ERR(strtype(ctx));
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of subtype definition");
}
} else {
CHECK_ERR(strtype(ctx));
}
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of type definition");
}
ctx.finishSubtype(name, pos);
return Ok{};
}
// deftype ::= '(' 'rec' subtype* ')'
// | subtype
template<typename Ctx> MaybeResult<> deftype(Ctx& ctx) {
auto pos = ctx.in.getPos();
if (ctx.in.takeSExprStart("rec"sv)) {
size_t startIndex = ctx.getRecGroupStartIndex();
size_t groupLen = 0;
while (auto type = subtype(ctx)) {
CHECK_ERR(type);
++groupLen;
}
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected type definition or end of recursion group");
}
ctx.addRecGroup(startIndex, groupLen);
} else if (auto type = subtype(ctx)) {
CHECK_ERR(type);
} else {
return {};
}
ctx.finishDeftype(pos);
return Ok{};
}
// local ::= '(' 'local id? t:valtype ')' => [t]
// | '(' 'local t*:valtype* ')' => [t*]
// locals ::= local*
template<typename Ctx> MaybeResult<typename Ctx::LocalsT> locals(Ctx& ctx) {
bool hasAny = false;
auto res = ctx.makeLocals();
while (ctx.in.takeSExprStart("local"sv)) {
hasAny = true;
if (auto id = ctx.in.takeID()) {
// Single named local
auto type = valtype(ctx);
CHECK_ERR(type);
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of local");
}
ctx.appendLocal(res, *id, *type);
} else {
// Repeated unnamed locals
while (!ctx.in.takeRParen()) {
auto type = valtype(ctx);
CHECK_ERR(type);
ctx.appendLocal(res, {}, *type);
}
}
}
if (hasAny) {
return res;
}
return {};
}
// func ::= '(' 'func' id? ('(' 'export' name ')')*
// x,I:typeuse t*:vec(local) (in:instr)* ')'
// | '(' 'func' id? ('(' 'export' name ')')*
// '(' 'import' mod:name nm:name ')' typeuse ')'
template<typename Ctx> MaybeResult<> func(Ctx& ctx) {
auto pos = ctx.in.getPos();
if (!ctx.in.takeSExprStart("func"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);
auto type = typeuse(ctx);
CHECK_ERR(type);
std::optional<typename Ctx::LocalsT> localVars;
if (!import) {
if (auto l = locals(ctx)) {
CHECK_ERR(l);
localVars = *l;
}
CHECK_ERR(instrs(ctx));
}
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of function");
}
CHECK_ERR(
ctx.addFunc(name, *exports, import.getPtr(), *type, localVars, pos));
return Ok{};
}
// mem ::= '(' 'memory' id? ('(' 'export' name ')')*
// ('(' 'data' b:datastring ')' | memtype) ')'
// | '(' 'memory' id? ('(' 'export' name ')')*
// '(' 'import' mod:name nm:name ')' memtype ')'
template<typename Ctx> MaybeResult<> memory(Ctx& ctx) {
auto pos = ctx.in.getPos();
if (!ctx.in.takeSExprStart("memory"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);
std::optional<typename Ctx::MemTypeT> mtype;
std::optional<typename Ctx::DataStringT> data;
if (ctx.in.takeSExprStart("data"sv)) {
if (import) {
return ctx.in.err("imported memories cannot have inline data");
}
auto datastr = datastring(ctx);
CHECK_ERR(datastr);
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of inline data");
}
mtype = ctx.makeMemType(Type::i32, ctx.getLimitsFromData(*datastr), false);
data = *datastr;
} else {
auto type = memtype(ctx);
CHECK_ERR(type);
mtype = *type;
}
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of memory declaration");
}
CHECK_ERR(ctx.addMemory(name, *exports, import.getPtr(), *mtype, pos));
if (data) {
CHECK_ERR(ctx.addImplicitData(std::move(*data)));
}
return Ok{};
}
// global ::= '(' 'global' id? ('(' 'export' name ')')* gt:globaltype e:expr ')'
// | '(' 'global' id? ('(' 'export' name ')')*
// '(' 'import' mod:name nm:name ')' gt:globaltype ')'
template<typename Ctx> MaybeResult<> global(Ctx& ctx) {
auto pos = ctx.in.getPos();
if (!ctx.in.takeSExprStart("global"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);
auto type = globaltype(ctx);
CHECK_ERR(type);
std::optional<typename Ctx::ExprT> exp;
if (!import) {
auto e = expr(ctx);
CHECK_ERR(e);
exp = *e;
}
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of global");
}
CHECK_ERR(ctx.addGlobal(name, *exports, import.getPtr(), *type, exp, pos));
return Ok{};
}
// datastring ::= (b:string)* => concat(b*)
template<typename Ctx> Result<typename Ctx::DataStringT> datastring(Ctx& ctx) {
auto data = ctx.makeDataString();
while (auto str = ctx.in.takeString()) {
ctx.appendDataString(data, *str);
}
return data;
}
// data ::= '(' 'data' id? b*:datastring ')' => {init b*, mode passive}
// | '(' 'data' id? x:memuse? ('(' 'offset' e:expr ')' | e:instr)
// b*:datastring ')
// => {init b*, mode active {memory x, offset e}}
template<typename Ctx> MaybeResult<> data(Ctx& ctx) {
auto pos = ctx.in.getPos();
if (!ctx.in.takeSExprStart("data"sv)) {
return {};
}
Name name;
if (auto id = ctx.in.takeID()) {
name = *id;
}
auto mem = maybeMemuse(ctx);
CHECK_ERR(mem);
std::optional<typename Ctx::ExprT> offset;
if (ctx.in.takeSExprStart("offset"sv)) {
auto e = expr(ctx);
CHECK_ERR(e);
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of offset expression");
}
offset = *e;
} else if (ctx.in.takeLParen()) {
CHECK_ERR(instr(ctx));
auto offsetExpr = ctx.makeExpr();
CHECK_ERR(offsetExpr);
offset = *offsetExpr;
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of offset instruction");
}
}
if (mem && !offset) {
return ctx.in.err("expected offset for active segment");
}
auto str = datastring(ctx);
CHECK_ERR(str);
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of data segment");
}
CHECK_ERR(ctx.addData(name, mem.getPtr(), offset, std::move(*str), pos));
return Ok{};
}
// modulefield ::= deftype
// | import
// | func
// | table
// | memory
// | global
// | export
// | start
// | elem
// | data
template<typename Ctx> MaybeResult<> modulefield(Ctx& ctx) {
if (auto t = ctx.in.peek(); !t || t->isRParen()) {
return {};
}
if (auto res = deftype(ctx)) {
CHECK_ERR(res);
return Ok{};
}
if (auto res = func(ctx)) {
CHECK_ERR(res);
return Ok{};
}
if (auto res = memory(ctx)) {
CHECK_ERR(res);
return Ok{};
}
if (auto res = global(ctx)) {
CHECK_ERR(res);
return Ok{};
}
if (auto res = data(ctx)) {
CHECK_ERR(res);
return Ok{};
}
return ctx.in.err("unrecognized module field");
}
// module ::= '(' 'module' id? (m:modulefield)* ')'
// | (m:modulefield)* eof
template<typename Ctx> Result<> module(Ctx& ctx) {
bool outer = ctx.in.takeSExprStart("module"sv);
if (outer) {
if (auto id = ctx.in.takeID()) {
ctx.wasm.name = *id;
}
}
while (auto field = modulefield(ctx)) {
CHECK_ERR(field);
}
if (outer && !ctx.in.takeRParen()) {
return ctx.in.err("expected end of module");
}
return Ok{};
}
} // namespace wasm::WATParser
#endif // parser_parsers_h