blob: 8d85e4f4511611dab805b2bab16b61ea933dddf0 [file] [log] [blame] [edit]
/*
* 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 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 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 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.
//
// The fifth and final phase 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 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;
explicit ParseInput(std::string_view in) : lexer(in) {}
ParseInput(std::string_view in, size_t index) : lexer(in) {
lexer.setIndex(index);
}
ParseInput(const ParseInput& other, size_t index) : lexer(other.lexer) {
lexer.setIndex(index);
}
bool empty() { return lexer.empty(); }
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;
}
bool takeUntilParen() {
while (true) {
auto t = peek();
if (!t) {
return false;
}
if (t->isLParen() || t->isRParen()) {
return true;
}
++lexer;
}
}
std::optional<Name> takeID() {
if (auto t = peek()) {
if (auto id = t->getID()) {
++lexer;
// See comment on takeName.
return Name(std::string(*id));
}
}
return {};
}
std::optional<std::string_view> takeKeyword() {
if (auto t = peek()) {
if (auto keyword = t->getKeyword()) {
++lexer;
return *keyword;
}
}
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<uint8_t> takeU8() {
if (auto t = peek()) {
if (auto n = t->getU32()) {
if (n <= std::numeric_limits<uint8_t>::max()) {
++lexer;
return uint8_t(*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(Index pos, std::string reason) {
std::stringstream msg;
msg << lexer.position(pos) << ": error: " << reason;
return Err{msg.str()};
}
[[nodiscard]] Err err(std::string reason) { return err(getPos(), reason); }
};
// =========
// Utilities
// =========
// 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;
};
// A signature type and parameter names (possibly empty), used for parsing
// function types.
struct TypeUse {
HeapType type;
std::vector<Name> names;
};
struct ImportNames {
Name mod;
Name nm;
};
struct Limits {
uint64_t initial;
uint64_t max;
};
struct MemType {
Type type;
Limits limits;
};
// 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>;
using IndexMap = std::unordered_map<Name, Index>;
void applyImportNames(Importable& item, 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<IndexMap> createIndexMap(ParseInput& in,
const std::vector<DefPos>& defs) {
IndexMap indices;
for (Index i = 0; i < defs.size(); ++i) {
if (defs[i].name.is()) {
if (!indices.insert({defs[i].name, i}).second) {
return in.err(defs[i].pos, "duplicate element name");
}
}
}
return indices;
}
std::vector<Type> getUnnamedTypes(const std::vector<NameType>& named) {
std::vector<Type> types;
types.reserve(named.size());
for (auto& t : named) {
types.push_back(t.type);
}
return types;
}
template<typename Ctx>
Result<> parseDefs(Ctx& ctx,
const std::vector<DefPos>& defs,
MaybeResult<> (*parser)(Ctx&)) {
for (Index i = 0; i < defs.size(); ++i) {
ctx.index = i;
WithPosition with(ctx, defs[i].pos);
auto parsed = parser(ctx);
CHECK_ERR(parsed);
assert(parsed);
}
return Ok{};
}
// ===============
// Parser Contexts
// ===============
struct NullTypeParserCtx {
using IndexT = Ok;
using HeapTypeT = Ok;
using TypeT = Ok;
using ParamsT = Ok;
using ResultsT = Ok;
using SignatureT = Ok;
using StorageT = Ok;
using FieldT = Ok;
using FieldsT = Ok;
using StructT = Ok;
using ArrayT = Ok;
using LimitsT = Ok;
using MemTypeT = Ok;
using GlobalTypeT = Ok;
using TypeUseT = Ok;
using LocalsT = Ok;
using DataStringT = Ok;
HeapTypeT makeFunc() { return Ok{}; }
HeapTypeT makeAny() { return Ok{}; }
HeapTypeT makeExtern() { return Ok{}; }
HeapTypeT makeEq() { return Ok{}; }
HeapTypeT makeI31() { return Ok{}; }
HeapTypeT makeData() { return Ok{}; }
TypeT makeI32() { return Ok{}; }
TypeT makeI64() { return Ok{}; }
TypeT makeF32() { return Ok{}; }
TypeT makeF64() { return Ok{}; }
TypeT makeV128() { return Ok{}; }
TypeT makeRefType(HeapTypeT, Nullability) { return Ok{}; }
ParamsT makeParams() { return Ok{}; }
void appendParam(ParamsT&, Name, TypeT) {}
ResultsT makeResults() { return Ok{}; }
void appendResult(ResultsT&, TypeT) {}
SignatureT makeFuncType(ParamsT*, ResultsT*) { return Ok{}; }
StorageT makeI8() { return Ok{}; }
StorageT makeI16() { return Ok{}; }
StorageT makeStorageType(TypeT) { return Ok{}; }
FieldT makeFieldType(StorageT, Mutability) { return Ok{}; }
FieldsT makeFields() { return Ok{}; }
void appendField(FieldsT&, Name, FieldT) {}
StructT makeStruct(FieldsT&) { return Ok{}; }
std::optional<ArrayT> makeArray(FieldsT&) { return Ok{}; }
GlobalTypeT makeGlobalType(Mutability, TypeT) { return Ok{}; }
LocalsT makeLocals() { return Ok{}; }
void appendLocal(LocalsT&, Name, TypeT) {}
Result<Index> getTypeIndex(Name) { return 1; }
Result<HeapTypeT> getHeapTypeFromIdx(Index) { return Ok{}; }
DataStringT makeDataString() { return Ok{}; }
void appendDataString(DataStringT&, std::string_view) {}
LimitsT makeLimits(uint64_t, std::optional<uint64_t>) { return Ok{}; }
LimitsT getLimitsFromData(DataStringT) { return Ok{}; }
MemTypeT makeMemType32(LimitsT) { return Ok{}; }
MemTypeT makeMemType64(LimitsT) { return Ok{}; }
};
template<typename Ctx> struct TypeParserCtx {
using IndexT = Index;
using HeapTypeT = HeapType;
using TypeT = Type;
using ParamsT = std::vector<NameType>;
using ResultsT = std::vector<Type>;
using SignatureT = Signature;
using StorageT = Field;
using FieldT = Field;
using FieldsT = std::pair<std::vector<Name>, std::vector<Field>>;
using StructT = std::pair<std::vector<Name>, Struct>;
using ArrayT = Array;
using LimitsT = Limits;
using MemTypeT = MemType;
using LocalsT = std::vector<NameType>;
using DataStringT = std::vector<char>;
// Map heap type names to their indices.
const IndexMap& typeIndices;
TypeParserCtx(const IndexMap& typeIndices) : typeIndices(typeIndices) {}
Ctx& self() { return *static_cast<Ctx*>(this); }
HeapTypeT makeFunc() { return HeapType::func; }
HeapTypeT makeAny() { return HeapType::any; }
HeapTypeT makeExtern() { return HeapType::ext; }
HeapTypeT makeEq() { return HeapType::eq; }
HeapTypeT makeI31() { return HeapType::i31; }
HeapTypeT makeData() { return HeapType::data; }
TypeT makeI32() { return Type::i32; }
TypeT makeI64() { return Type::i64; }
TypeT makeF32() { return Type::f32; }
TypeT makeF64() { return Type::f64; }
TypeT makeV128() { return Type::v128; }
TypeT makeRefType(HeapTypeT ht, Nullability nullability) {
return Type(ht, nullability);
}
TypeT makeTupleType(const std::vector<Type> types) { return Tuple(types); }
ParamsT makeParams() { return {}; }
void appendParam(ParamsT& params, Name id, TypeT type) {
params.push_back({id, type});
}
ResultsT makeResults() { return {}; }
void appendResult(ResultsT& results, TypeT type) { results.push_back(type); }
SignatureT makeFuncType(ParamsT* params, ResultsT* results) {
std::vector<Type> empty;
const auto& paramTypes = params ? getUnnamedTypes(*params) : empty;
const auto& resultTypes = results ? *results : empty;
return Signature(self().makeTupleType(paramTypes),
self().makeTupleType(resultTypes));
}
StorageT makeI8() { return Field(Field::i8, Immutable); }
StorageT makeI16() { return Field(Field::i16, Immutable); }
StorageT makeStorageType(TypeT type) { return Field(type, Immutable); }
FieldT makeFieldType(FieldT field, Mutability mutability) {
if (field.packedType == Field::not_packed) {
return Field(field.type, mutability);
}
return Field(field.packedType, mutability);
}
FieldsT makeFields() { return {}; }
void appendField(FieldsT& fields, Name name, FieldT field) {
fields.first.push_back(name);
fields.second.push_back(field);
}
StructT makeStruct(FieldsT& fields) {
return {std::move(fields.first), Struct(std::move(fields.second))};
}
std::optional<ArrayT> makeArray(FieldsT& fields) {
if (fields.second.size() == 1) {
return Array(fields.second[0]);
}
return {};
}
LocalsT makeLocals() { return {}; }
void appendLocal(LocalsT& locals, Name id, TypeT type) {
locals.push_back({id, type});
}
Result<Index> getTypeIndex(Name id) {
auto it = typeIndices.find(id);
if (it == typeIndices.end()) {
return self().in.err("unknown type identifier");
}
return it->second;
}
std::vector<char> makeDataString() { return {}; }
void appendDataString(std::vector<char>& data, std::string_view str) {
data.insert(data.end(), str.begin(), str.end());
}
Limits makeLimits(uint64_t n, std::optional<uint64_t> m) {
return m ? Limits{n, *m} : Limits{n, Memory::kUnlimitedSize};
}
Limits getLimitsFromData(const std::vector<char>& data) {
uint64_t size = (data.size() + Memory::kPageSize - 1) / Memory::kPageSize;
return {size, size};
}
MemType makeMemType32(Limits limits) { return {Type::i32, limits}; }
MemType makeMemType64(Limits limits) { return {Type::i64, limits}; }
};
struct NullInstrParserCtx {
using InstrT = Ok;
using InstrsT = Ok;
using ExprT = Ok;
using LocalT = Ok;
using GlobalT = Ok;
using MemoryT = Ok;
InstrsT makeInstrs() { return Ok{}; }
void appendInstr(InstrsT&, InstrT) {}
InstrsT finishInstrs(InstrsT&) { return Ok{}; }
ExprT makeExpr(InstrsT) { return Ok{}; }
LocalT getLocalFromIdx(uint32_t) { return Ok{}; }
LocalT getLocalFromName(Name) { return Ok{}; }
GlobalT getGlobalFromIdx(uint32_t) { return Ok{}; }
GlobalT getGlobalFromName(Name) { return Ok{}; }
MemoryT getMemoryFromIdx(uint32_t) { return Ok{}; }
MemoryT getMemoryFromName(Name) { return Ok{}; }
InstrT makeUnreachable(Index) { return Ok{}; }
InstrT makeNop(Index) { return Ok{}; }
InstrT makeBinary(Index, BinaryOp) { return Ok{}; }
InstrT makeUnary(Index, UnaryOp) { return Ok{}; }
template<typename ResultsT> InstrT makeSelect(Index, ResultsT*) {
return Ok{};
}
InstrT makeDrop(Index) { return Ok{}; }
InstrT makeMemorySize(Index, MemoryT*) { return Ok{}; }
InstrT makeMemoryGrow(Index, MemoryT*) { return Ok{}; }
InstrT makeLocalGet(Index, LocalT) { return Ok{}; }
InstrT makeLocalTee(Index, LocalT) { return Ok{}; }
InstrT makeLocalSet(Index, LocalT) { return Ok{}; }
InstrT makeGlobalGet(Index, GlobalT) { return Ok{}; }
InstrT makeGlobalSet(Index, GlobalT) { return Ok{}; }
InstrT makeI32Const(Index, uint32_t) { return Ok{}; }
InstrT makeI64Const(Index, uint64_t) { return Ok{}; }
InstrT makeF32Const(Index, float) { return Ok{}; }
InstrT makeF64Const(Index, double) { return Ok{}; }
InstrT makeSIMDExtract(Index, SIMDExtractOp, uint8_t) { return Ok{}; }
InstrT makeSIMDReplace(Index, SIMDReplaceOp, uint8_t) { return Ok{}; }
InstrT makeSIMDShuffle(Index, const std::array<uint8_t, 16>&) { return Ok{}; }
InstrT makeSIMDTernary(Index, SIMDTernaryOp) { return Ok{}; }
InstrT makeSIMDShift(Index, SIMDShiftOp) { return Ok{}; }
template<typename HeapTypeT> InstrT makeRefNull(Index, HeapTypeT) {
return {};
}
};
template<typename Ctx> struct InstrParserCtx : TypeParserCtx<Ctx> {
// Keep track of instructions internally rather than letting the general
// parser collect them.
using InstrT = Ok;
using InstrsT = std::vector<Expression*>;
using ExprT = Expression*;
using LocalT = Index;
using GlobalT = Name;
using MemoryT = Name;
Builder builder;
// The stack of parsed expressions, used as the children of newly parsed
// expressions.
std::vector<Expression*> exprStack;
// Whether we have seen an unreachable instruction and are in
// stack-polymorphic unreachable mode.
bool unreachable = false;
// The expected result type of the instruction sequence we are parsing.
Type type;
InstrParserCtx(Module& wasm, const IndexMap& typeIndices)
: TypeParserCtx<Ctx>(typeIndices), builder(wasm) {}
Ctx& self() { return *static_cast<Ctx*>(this); }
Result<> push(Index pos, Expression* expr) {
if (expr->type == Type::unreachable) {
// We want to avoid popping back past this most recent unreachable
// instruction. Drop all prior instructions so they won't be consumed by
// later instructions but will still be emitted for their side effects, if
// any.
for (auto& expr : exprStack) {
expr = builder.dropIfConcretelyTyped(expr);
}
unreachable = true;
exprStack.push_back(expr);
} else if (expr->type.isTuple()) {
auto scratchIdx = self().addScratchLocal(pos, expr->type);
CHECK_ERR(scratchIdx);
CHECK_ERR(push(pos, builder.makeLocalSet(*scratchIdx, expr)));
for (Index i = 0; i < expr->type.size(); ++i) {
CHECK_ERR(push(pos,
builder.makeTupleExtract(
builder.makeLocalGet(*scratchIdx, expr->type[i]), i)));
}
} else {
exprStack.push_back(expr);
}
return Ok{};
}
Result<Expression*> pop(Index pos) {
// Find the suffix of expressions that do not produce values.
auto firstNone = exprStack.size();
for (; firstNone > 0; --firstNone) {
auto* expr = exprStack[firstNone - 1];
if (expr->type != Type::none) {
break;
}
}
if (firstNone == 0) {
// There are no expressions that produce values.
if (unreachable) {
return builder.makeUnreachable();
}
return self().in.err(pos, "popping from empty stack");
}
if (firstNone == exprStack.size()) {
// The last expression produced a value.
auto expr = exprStack.back();
exprStack.pop_back();
return expr;
}
// We need to assemble a block of expressions that returns the value of the
// first one using a scratch local (unless it's unreachable, in which case
// we can throw the following expressions away).
auto* expr = exprStack[firstNone - 1];
if (expr->type == Type::unreachable) {
exprStack.resize(firstNone - 1);
return expr;
}
auto scratchIdx = self().addScratchLocal(pos, expr->type);
CHECK_ERR(scratchIdx);
std::vector<Expression*> exprs;
exprs.reserve(exprStack.size() - firstNone + 2);
exprs.push_back(builder.makeLocalSet(*scratchIdx, expr));
exprs.insert(exprs.end(), exprStack.begin() + firstNone, exprStack.end());
exprs.push_back(builder.makeLocalGet(*scratchIdx, expr->type));
exprStack.resize(firstNone - 1);
return builder.makeBlock(exprs, expr->type);
}
Ok makeInstrs() { return Ok{}; }
void appendInstr(Ok&, InstrT instr) {}
Result<InstrsT> finishInstrs(Ok&) {
// We have finished parsing a sequence of instructions. Fix up the parsed
// instructions and reset the context for the next sequence.
if (type.isTuple()) {
std::vector<Expression*> elems(type.size());
for (size_t i = 0; i < elems.size(); ++i) {
auto elem = pop(self().in.getPos());
CHECK_ERR(elem);
elems[elems.size() - 1 - i] = *elem;
}
exprStack.push_back(builder.makeTupleMake(std::move(elems)));
} else if (type != Type::none) {
// Ensure the last expression produces the value.
auto expr = pop(self().in.getPos());
CHECK_ERR(expr);
exprStack.push_back(*expr);
}
unreachable = false;
return std::move(exprStack);
}
ExprT makeExpr(InstrsT& instrs) {
switch (instrs.size()) {
case 0:
return builder.makeNop();
case 1:
return instrs.front();
default:
return builder.makeBlock(instrs);
}
}
Result<> makeUnreachable(Index pos) {
return push(pos, builder.makeUnreachable());
}
Result<> makeNop(Index pos) { return push(pos, builder.makeNop()); }
Result<> makeBinary(Index pos, BinaryOp op) {
auto rhs = pop(pos);
CHECK_ERR(rhs);
auto lhs = pop(pos);
CHECK_ERR(lhs);
return push(pos, builder.makeBinary(op, *lhs, *rhs));
}
Result<> makeUnary(Index pos, UnaryOp op) {
auto val = pop(pos);
CHECK_ERR(val);
return push(pos, builder.makeUnary(op, *val));
}
Result<> makeSelect(Index pos, std::vector<Type>* res) {
if (res && res->size() > 1) {
return self().in.err(pos,
"select may not have more than one result type");
}
auto cond = pop(pos);
CHECK_ERR(cond);
auto ifFalse = pop(pos);
CHECK_ERR(ifFalse);
auto ifTrue = pop(pos);
CHECK_ERR(ifTrue);
auto select = builder.makeSelect(*cond, *ifTrue, *ifFalse);
if (res && !res->empty() && !Type::isSubType(select->type, res->front())) {
return self().in.err(pos, "select type annotation is incorrect");
}
return push(pos, builder.makeSelect(*cond, *ifTrue, *ifFalse));
}
Result<> makeDrop(Index pos) {
auto val = pop(pos);
CHECK_ERR(val);
return push(pos, builder.makeDrop(*val));
}
Result<> makeMemorySize(Index pos, Name* mem) {
auto m = self().getMemory(pos, mem);
CHECK_ERR(m);
return push(pos, builder.makeMemorySize(*m));
}
Result<> makeMemoryGrow(Index pos, Name* mem) {
auto m = self().getMemory(pos, mem);
CHECK_ERR(m);
auto val = pop(pos);
CHECK_ERR(val);
return push(pos, builder.makeMemoryGrow(*val, *m));
}
Result<> makeI32Const(Index pos, uint32_t c) {
return push(pos, builder.makeConst(Literal(c)));
}
Result<> makeI64Const(Index pos, uint64_t c) {
return push(pos, builder.makeConst(Literal(c)));
}
Result<> makeF32Const(Index pos, float c) {
return push(pos, builder.makeConst(Literal(c)));
}
Result<> makeF64Const(Index pos, double c) {
return push(pos, builder.makeConst(Literal(c)));
}
Result<> makeRefNull(Index pos, HeapType type) {
return push(pos, builder.makeRefNull(type));
}
};
// Phase 1: Parse definition spans for top-level module elements and determine
// their indices and names.
struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx {
ParseInput in;
// 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.
//
// 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> typeDefs;
std::vector<DefPos> subtypeDefs;
std::vector<DefPos> funcDefs;
std::vector<DefPos> memoryDefs;
std::vector<DefPos> globalDefs;
// Positions of typeuses that might implicitly define new types.
std::vector<Index> implicitTypeDefs;
// Counters used for generating names for module elements.
int funcCounter = 0;
int memoryCounter = 0;
int globalCounter = 0;
// Used to verify that all imports come before all non-imports.
bool hasNonImport = false;
ParseDeclsCtx(std::string_view in, Module& wasm) : in(in), wasm(wasm) {}
void addFuncType(SignatureT) {}
void addStructType(StructT) {}
void addArrayType(ArrayT) {}
Result<> addSubtype(Index) { return Ok{}; }
void finishSubtype(Name name, Index pos) {
subtypeDefs.push_back({name, pos});
}
size_t getRecGroupStartIndex() { return 0; }
void addRecGroup(Index, size_t) {}
void finishDeftype(Index pos) { typeDefs.push_back({{}, pos}); }
Result<TypeUseT>
makeTypeUse(Index pos, std::optional<HeapTypeT> type, ParamsT*, ResultsT*) {
if (!type) {
implicitTypeDefs.push_back(pos);
}
return Ok{};
}
Result<Function*>
addFuncDecl(Index pos, Name name, ImportNames* importNames) {
auto f = std::make_unique<Function>();
if (name.is()) {
if (wasm.getFunctionOrNull(name)) {
// TDOO: if the existing function is not explicitly named, fix its name
// and continue.
return in.err(pos, "repeated function name");
}
f->setExplicitName(name);
} else {
name = (importNames ? "fimport$" : "") + std::to_string(funcCounter++);
name = Names::getValidFunctionName(wasm, name);
f->name = name;
}
applyImportNames(*f, importNames);
return wasm.addFunction(std::move(f));
}
Result<> addFunc(Name name,
const std::vector<Name>& exports,
ImportNames* import,
TypeUseT type,
std::optional<LocalsT>,
std::optional<InstrsT>,
Index pos) {
if (import && hasNonImport) {
return in.err("import after non-import");
}
auto f = addFuncDecl(pos, name, import);
CHECK_ERR(f);
CHECK_ERR(addExports(in, wasm, *f, exports, ExternalKind::Function));
funcDefs.push_back({name, pos});
return Ok{};
}
Result<Memory*>
addMemoryDecl(Index pos, Name name, ImportNames* importNames) {
auto m = std::make_unique<Memory>();
if (name) {
// TODO: if the existing memory is not explicitly named, fix its name
// and continue.
if (wasm.getMemoryOrNull(name)) {
return in.err(pos, "repeated memory name");
}
m->setExplicitName(name);
} else {
name = (importNames ? "mimport$" : "") + std::to_string(memoryCounter++);
name = Names::getValidMemoryName(wasm, name);
m->name = name;
}
applyImportNames(*m, importNames);
return wasm.addMemory(std::move(m));
}
Result<> addMemory(Name name,
const std::vector<Name>& exports,
ImportNames* import,
MemTypeT,
Index pos) {
if (import && hasNonImport) {
return in.err(pos, "import after non-import");
}
auto m = addMemoryDecl(pos, name, import);
CHECK_ERR(m);
CHECK_ERR(addExports(in, wasm, *m, exports, ExternalKind::Memory));
memoryDefs.push_back({name, pos});
return Ok{};
}
Result<Global*>
addGlobalDecl(Index pos, Name name, ImportNames* importNames) {
auto g = std::make_unique<Global>();
if (name) {
if (wasm.getGlobalOrNull(name)) {
// TODO: if the existing global is not explicitly named, fix its name
// and continue.
return in.err(pos, "repeated global name");
}
g->setExplicitName(name);
} else {
name = (importNames ? "gimport$" : "") + std::to_string(globalCounter++);
name = Names::getValidGlobalName(wasm, name);
g->name = name;
}
applyImportNames(*g, importNames);
return wasm.addGlobal(std::move(g));
}
Result<> addGlobal(Name name,
const std::vector<Name>& exports,
ImportNames* import,
GlobalTypeT,
std::optional<ExprT>,
Index pos) {
if (import && hasNonImport) {
return in.err(pos, "import after non-import");
}
auto g = addGlobalDecl(pos, name, import);
CHECK_ERR(g);
CHECK_ERR(addExports(in, wasm, *g, exports, ExternalKind::Global));
globalDefs.push_back({name, pos});
return Ok{};
}
};
// Phase 2: Parse type definitions into a TypeBuilder.
struct ParseTypeDefsCtx : TypeParserCtx<ParseTypeDefsCtx> {
ParseInput in;
// We update slots in this builder as we parse type definitions.
TypeBuilder& builder;
// Parse the names of types and fields as we go.
std::vector<TypeNames> names;
// The index of the subtype definition we are parsing.
Index index = 0;
ParseTypeDefsCtx(std::string_view in,
TypeBuilder& builder,
const IndexMap& typeIndices)
: TypeParserCtx<ParseTypeDefsCtx>(typeIndices), in(in), builder(builder),
names(builder.size()) {}
TypeT makeRefType(HeapTypeT ht, Nullability nullability) {
return builder.getTempRefType(ht, nullability);
}
TypeT makeTupleType(const std::vector<Type> types) {
return builder.getTempTupleType(types);
}
Result<HeapTypeT> getHeapTypeFromIdx(Index idx) {
if (idx >= builder.size()) {
return in.err("type index out of bounds");
}
return builder[idx];
}
void addFuncType(SignatureT& type) { builder[index] = type; }
void addStructType(StructT& type) {
auto& [fieldNames, str] = type;
builder[index] = str;
for (Index i = 0; i < fieldNames.size(); ++i) {
if (auto name = fieldNames[i]; name.is()) {
names[index].fieldNames[i] = name;
}
}
}
void addArrayType(ArrayT& type) { builder[index] = type; }
Result<> addSubtype(Index super) {
if (super >= builder.size()) {
return in.err("supertype index out of bounds");
}
builder[index].subTypeOf(builder[super]);
return Ok{};
}
void finishSubtype(Name name, Index pos) { names[index++].name = name; }
size_t getRecGroupStartIndex() { return index; }
void addRecGroup(Index start, size_t len) {
builder.createRecGroup(start, len);
}
void finishDeftype(Index) {}
};
// Phase 3: Parse type uses to find implicitly defined types.
struct ParseImplicitTypeDefsCtx : TypeParserCtx<ParseImplicitTypeDefsCtx> {
using TypeUseT = Ok;
ParseInput in;
// Types parsed so far.
std::vector<HeapType>& types;
// Map typeuse positions without an explicit type to the correct type.
std::unordered_map<Index, HeapType>& implicitTypes;
// Map signatures to the first defined heap type they match.
std::unordered_map<Signature, HeapType> sigTypes;
ParseImplicitTypeDefsCtx(std::string_view in,
std::vector<HeapType>& types,
std::unordered_map<Index, HeapType>& implicitTypes,
const IndexMap& typeIndices)
: TypeParserCtx<ParseImplicitTypeDefsCtx>(typeIndices), in(in),
types(types), implicitTypes(implicitTypes) {
for (auto type : types) {
if (type.isSignature() && type.getRecGroup().size() == 1) {
sigTypes.insert({type.getSignature(), type});
}
}
}
Result<HeapTypeT> getHeapTypeFromIdx(Index idx) {
if (idx >= types.size()) {
return in.err("type index out of bounds");
}
return types[idx];
}
Result<TypeUseT> makeTypeUse(Index pos,
std::optional<HeapTypeT>,
ParamsT* params,
ResultsT* results) {
std::vector<Type> paramTypes;
if (params) {
paramTypes = getUnnamedTypes(*params);
}
std::vector<Type> resultTypes;
if (results) {
resultTypes = *results;
}
auto sig = Signature(Type(paramTypes), Type(resultTypes));
auto [it, inserted] = sigTypes.insert({sig, HeapType::func});
if (inserted) {
auto type = HeapType(sig);
it->second = type;
types.push_back(type);
}
implicitTypes.insert({pos, it->second});
return Ok{};
}
};
// Phase 4: Parse and set the types of module elements.
struct ParseModuleTypesCtx : TypeParserCtx<ParseModuleTypesCtx>,
NullInstrParserCtx {
// In this phase we have constructed all the types, so we can materialize and
// validate them when they are used.
using GlobalTypeT = GlobalType;
using TypeUseT = TypeUse;
ParseInput in;
Module& wasm;
const std::vector<HeapType>& types;
const std::unordered_map<Index, HeapType>& implicitTypes;
// The index of the current type.
Index index = 0;
ParseModuleTypesCtx(std::string_view in,
Module& wasm,
const std::vector<HeapType>& types,
const std::unordered_map<Index, HeapType>& implicitTypes,
const IndexMap& typeIndices)
: TypeParserCtx<ParseModuleTypesCtx>(typeIndices), in(in), wasm(wasm),
types(types), implicitTypes(implicitTypes) {}
Result<HeapTypeT> getHeapTypeFromIdx(Index idx) {
if (idx >= types.size()) {
return in.err("type index out of bounds");
}
return types[idx];
}
Result<TypeUseT> makeTypeUse(Index pos,
std::optional<HeapTypeT> type,
ParamsT* params,
ResultsT* results) {
std::vector<Name> ids;
if (params) {
ids.reserve(params->size());
for (auto& p : *params) {
ids.push_back(p.name);
}
}
if (type) {
return TypeUse{*type, ids};
}
auto it = implicitTypes.find(pos);
assert(it != implicitTypes.end());
return TypeUse{it->second, ids};
}
GlobalTypeT makeGlobalType(Mutability mutability, TypeT type) {
return {mutability, type};
}
Result<> addFunc(Name name,
const std::vector<Name>&,
ImportNames*,
TypeUse type,
std::optional<LocalsT> locals,
std::optional<InstrsT>,
Index) {
auto& f = wasm.functions[index];
f->type = type.type;
for (Index i = 0; i < type.names.size(); ++i) {
if (type.names[i].is()) {
f->setLocalName(i, type.names[i]);
}
}
if (locals) {
for (auto& l : *locals) {
Builder::addVar(f.get(), l.name, l.type);
}
}
return Ok{};
}
Result<> addMemory(
Name, const std::vector<Name>&, ImportNames*, MemType type, Index pos) {
auto& m = wasm.memories[index];
m->indexType = type.type;
m->initial = type.limits.initial;
m->max = type.limits.max;
// TODO: shared memories.
m->shared = false;
return Ok{};
}
Result<> addGlobal(Name,
const std::vector<Name>&,
ImportNames*,
GlobalType type,
std::optional<ExprT>,
Index) {
auto& g = wasm.globals[index];
g->mutable_ = type.mutability;
g->type = type.type;
return Ok{};
}
};
// Phase 5: Parse module element definitions, including instructions.
struct ParseDefsCtx : InstrParserCtx<ParseDefsCtx> {
using GlobalTypeT = Ok;
using TypeUseT = HeapType;
ParseInput in;
Module& wasm;
const std::vector<HeapType>& types;
const std::unordered_map<Index, HeapType>& implicitTypes;
// The index of the current module element.
Index index = 0;
// The current function being parsed, used to create scratch locals, type
// local.get, etc.
Function* func = nullptr;
ParseDefsCtx(std::string_view in,
Module& wasm,
const std::vector<HeapType>& types,
const std::unordered_map<Index, HeapType>& implicitTypes,
const IndexMap& typeIndices)
: InstrParserCtx<ParseDefsCtx>(wasm, typeIndices), in(in), wasm(wasm),
types(types), implicitTypes(implicitTypes) {}
GlobalTypeT makeGlobalType(Mutability, TypeT) { return Ok{}; }
Result<HeapTypeT> getHeapTypeFromIdx(Index idx) {
if (idx >= types.size()) {
return in.err("type index out of bounds");
}
return types[idx];
}
Result<Index> getLocalFromIdx(uint32_t idx) {
if (!func) {
return in.err("cannot access locals outside of a funcion");
}
if (idx >= func->getNumLocals()) {
return in.err("local index out of bounds");
}
return idx;
}
Result<Index> getLocalFromName(Name name) {
if (!func) {
return in.err("cannot access locals outside of a function");
}
if (!func->hasLocalIndex(name)) {
return in.err("local $" + name.toString() + " does not exist");
}
return func->getLocalIndex(name);
}
Result<Name> getGlobalFromIdx(uint32_t idx) {
if (idx >= wasm.globals.size()) {
return in.err("global index out of bounds");
}
return wasm.globals[idx]->name;
}
Result<Name> getGlobalFromName(Name name) {
if (!wasm.getGlobalOrNull(name)) {
return in.err("global $" + name.toString() + " does not exist");
}
return name;
}
Result<Name> getMemoryFromIdx(uint32_t idx) {
if (idx >= wasm.memories.size()) {
return in.err("memory index out of bounds");
}
return wasm.memories[idx]->name;
}
Result<Name> getMemoryFromName(Name name) {
if (!wasm.getMemoryOrNull(name)) {
return in.err("memory $" + name.toString() + " does not exist");
}
return name;
}
Result<TypeUseT> makeTypeUse(Index pos,
std::optional<HeapTypeT> type,
ParamsT* params,
ResultsT* results) {
if (type && (params || results)) {
std::vector<Type> paramTypes;
if (params) {
paramTypes = getUnnamedTypes(*params);
}
std::vector<Type> resultTypes;
if (results) {
resultTypes = *results;
}
auto sig = Signature(Type(paramTypes), Type(resultTypes));
if (!type->isSignature() || type->getSignature() != sig) {
return in.err(pos, "type does not match provided signature");
}
}
if (type) {
return *type;
}
auto it = implicitTypes.find(pos);
assert(it != implicitTypes.end());
return it->second;
}
Result<> addFunc(Name,
const std::vector<Name>&,
ImportNames*,
TypeUseT,
std::optional<LocalsT>,
std::optional<InstrsT> insts,
Index) {
Expression* body;
if (insts) {
switch (insts->size()) {
case 0:
body = builder.makeNop();
break;
case 1:
body = insts->back();
break;
default:
body = builder.makeBlock(*insts, wasm.functions[index]->getResults());
break;
}
} else {
body = builder.makeNop();
}
wasm.functions[index]->body = body;
return Ok{};
}
Result<> addGlobal(Name,
const std::vector<Name>&,
ImportNames*,
GlobalTypeT,
std::optional<ExprT> exp,
Index) {
if (exp) {
wasm.globals[index]->init = *exp;
}
return Ok{};
}
Result<Index> addScratchLocal(Index pos, Type type) {
if (!func) {
return in.err(pos,
"scratch local required, but there is no function context");
}
Name name = Names::getValidLocalName(*func, "scratch");
return Builder::addVar(func, name, type);
}
Result<Name> getMemory(Index pos, Name* mem) {
if (mem) {
return *mem;
}
if (wasm.memories.empty()) {
return in.err(pos, "memory required, but there is no memory");
}
return wasm.memories[0]->name;
}
Result<> makeLocalGet(Index pos, Index local) {
if (!func) {
return in.err(pos, "local.get must be inside a function");
}
assert(local < func->getNumLocals());
return push(pos, builder.makeLocalGet(local, func->getLocalType(local)));
}
Result<> makeLocalTee(Index pos, Index local) {
if (!func) {
return in.err(pos, "local.tee must be inside a function");
}
assert(local < func->getNumLocals());
auto val = pop(pos);
CHECK_ERR(val);
return push(pos,
builder.makeLocalTee(local, *val, func->getLocalType(local)));
}
Result<> makeLocalSet(Index pos, Index local) {
if (!func) {
return in.err(pos, "local.set must be inside a function");
}
assert(local < func->getNumLocals());
auto val = pop(pos);
CHECK_ERR(val);
return push(pos, builder.makeLocalSet(local, *val));
}
Result<> makeGlobalGet(Index pos, Name global) {
assert(wasm.getGlobalOrNull(global));
auto type = wasm.getGlobal(global)->type;
return push(pos, builder.makeGlobalGet(global, type));
}
Result<> makeGlobalSet(Index pos, Name global) {
assert(wasm.getGlobalOrNull(global));
auto val = pop(pos);
CHECK_ERR(val);
return push(pos, builder.makeGlobalSet(global, *val));
}
Result<> makeSIMDExtract(Index pos, SIMDExtractOp op, uint8_t lane) {
auto val = pop(pos);
CHECK_ERR(val);
return push(pos, builder.makeSIMDExtract(op, *val, lane));
}
Result<> makeSIMDReplace(Index pos, SIMDReplaceOp op, uint8_t lane) {
auto val = pop(pos);
CHECK_ERR(val);
auto vec = pop(pos);
CHECK_ERR(vec);
return push(pos, builder.makeSIMDReplace(op, *vec, lane, *val));
}
Result<> makeSIMDShuffle(Index pos, const std::array<uint8_t, 16>& lanes) {
auto rhs = pop(pos);
CHECK_ERR(rhs);
auto lhs = pop(pos);
CHECK_ERR(lhs);
return push(pos, builder.makeSIMDShuffle(*lhs, *rhs, lanes));
}
Result<> makeSIMDTernary(Index pos, SIMDTernaryOp op) {
auto c = pop(pos);
CHECK_ERR(c);
auto b = pop(pos);
CHECK_ERR(b);
auto a = pop(pos);
CHECK_ERR(a);
return push(pos, builder.makeSIMDTernary(op, *a, *b, *c));
}
Result<> makeSIMDShift(Index pos, SIMDShiftOp op) {
auto shift = pop(pos);
CHECK_ERR(shift);
auto vec = pop(pos);
CHECK_ERR(vec);
return push(pos, builder.makeSIMDShift(op, *vec, *shift));
}
};
// ================
// Parser Functions
// ================
// 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<typename Ctx::InstrT> instr(Ctx&);
template<typename Ctx> Result<typename Ctx::InstrsT> instrs(Ctx&);
template<typename Ctx> Result<typename Ctx::ExprT> expr(Ctx&);
template<typename Ctx>
Result<typename Ctx::InstrT> makeUnreachable(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeNop(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeBinary(Ctx&, Index, BinaryOp op);
template<typename Ctx>
Result<typename Ctx::InstrT> makeUnary(Ctx&, Index, UnaryOp op);
template<typename Ctx> Result<typename Ctx::InstrT> makeSelect(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeDrop(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeMemorySize(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeMemoryGrow(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeLocalGet(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeLocalTee(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeLocalSet(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeGlobalGet(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeGlobalSet(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeBlock(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeThenOrElse(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeConst(Ctx&, Index, Type type);
template<typename Ctx>
Result<typename Ctx::InstrT> makeLoad(Ctx&, Index, Type type, bool isAtomic);
template<typename Ctx>
Result<typename Ctx::InstrT> makeStore(Ctx&, Index, Type type, bool isAtomic);
template<typename Ctx>
Result<typename Ctx::InstrT> makeAtomicRMWOrCmpxchg(Ctx&, Index, Type type);
template<typename Ctx>
Result<typename Ctx::InstrT>
makeAtomicRMW(Ctx&, Index, Type type, uint8_t bytes, const char* extra);
template<typename Ctx>
Result<typename Ctx::InstrT>
makeAtomicCmpxchg(Ctx&, Index, Type type, uint8_t bytes, const char* extra);
template<typename Ctx>
Result<typename Ctx::InstrT> makeAtomicWait(Ctx&, Index, Type type);
template<typename Ctx>
Result<typename Ctx::InstrT> makeAtomicNotify(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeAtomicFence(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT>
makeSIMDExtract(Ctx&, Index, SIMDExtractOp op, size_t lanes);
template<typename Ctx>
Result<typename Ctx::InstrT>
makeSIMDReplace(Ctx&, Index, SIMDReplaceOp op, size_t lanes);
template<typename Ctx>
Result<typename Ctx::InstrT> makeSIMDShuffle(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeSIMDTernary(Ctx&, Index, SIMDTernaryOp op);
template<typename Ctx>
Result<typename Ctx::InstrT> makeSIMDShift(Ctx&, Index, SIMDShiftOp op);
template<typename Ctx>
Result<typename Ctx::InstrT> makeSIMDLoad(Ctx&, Index, SIMDLoadOp op);
template<typename Ctx>
Result<typename Ctx::InstrT>
makeSIMDLoadStoreLane(Ctx&, Index, SIMDLoadStoreLaneOp op);
template<typename Ctx> Result<typename Ctx::InstrT> makeMemoryInit(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeDataDrop(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeMemoryCopy(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeMemoryFill(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makePop(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeIf(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeMaybeBlock(Ctx&, Index, size_t i, Type type);
template<typename Ctx> Result<typename Ctx::InstrT> makeLoop(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeCall(Ctx&, Index, bool isReturn);
template<typename Ctx>
Result<typename Ctx::InstrT> makeCallIndirect(Ctx&, Index, bool isReturn);
template<typename Ctx> Result<typename Ctx::InstrT> makeBreak(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeBreakTable(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeReturn(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeRefNull(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefIs(Ctx&, Index, RefIsOp op);
template<typename Ctx> Result<typename Ctx::InstrT> makeRefFunc(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeRefEq(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeTableGet(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeTableSet(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeTableSize(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeTableGrow(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeTry(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT>
makeTryOrCatchBody(Ctx&, Index, Type type, bool isTry);
template<typename Ctx> Result<typename Ctx::InstrT> makeThrow(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeRethrow(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeTupleMake(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeTupleExtract(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeCallRef(Ctx&, Index, bool isReturn);
template<typename Ctx> Result<typename Ctx::InstrT> makeI31New(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeI31Get(Ctx&, Index, bool signed_);
template<typename Ctx> Result<typename Ctx::InstrT> makeRefTest(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefTestStatic(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeRefCast(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefCastStatic(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefCastNopStatic(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeBrOn(Ctx&, Index, BrOnOp op);
template<typename Ctx>
Result<typename Ctx::InstrT> makeBrOnStatic(Ctx&, Index, BrOnOp op);
template<typename Ctx>
Result<typename Ctx::InstrT> makeStructNewStatic(Ctx&, Index, bool default_);
template<typename Ctx>
Result<typename Ctx::InstrT> makeStructGet(Ctx&, Index, bool signed_ = false);
template<typename Ctx> Result<typename Ctx::InstrT> makeStructSet(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeArrayNewStatic(Ctx&, Index, bool default_);
template<typename Ctx>
Result<typename Ctx::InstrT> makeArrayInitStatic(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeArrayGet(Ctx&, Index, bool signed_ = false);
template<typename Ctx> Result<typename Ctx::InstrT> makeArraySet(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeArrayLen(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeArrayCopy(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefAs(Ctx&, Index, RefAsOp op);
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringNew(Ctx&, Index, StringNewOp op);
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringConst(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringMeasure(Ctx&, Index, StringMeasureOp op);
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringEncode(Ctx&, Index, StringEncodeOp op);
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringConcat(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeStringEq(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringAs(Ctx&, Index, StringAsOp op);
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringWTF8Advance(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringWTF16Get(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringIterNext(Ctx&, Index);
template<typename Ctx>
Result<typename Ctx::InstrT>
makeStringIterMove(Ctx&, Index, StringIterMoveOp op);
template<typename Ctx>
Result<typename Ctx::InstrT>
makeStringSliceWTF(Ctx&, Index, StringSliceWTFOp op);
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringSliceIter(Ctx&, Index);
// Modules
template<typename Ctx> MaybeResult<Index> maybeTypeidx(Ctx& ctx);
template<typename Ctx> Result<typename Ctx::HeapTypeT> typeidx(Ctx&);
template<typename Ctx> MaybeResult<typename Ctx::MemoryT> maybeMemidx(Ctx&);
template<typename Ctx> Result<typename Ctx::MemoryT> memidx(Ctx&);
template<typename Ctx> Result<typename Ctx::GlobalT> globalidx(Ctx&);
template<typename Ctx> Result<typename Ctx::LocalT> localidx(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&);
MaybeResult<> modulefield(ParseDeclsCtx&);
Result<> module(ParseDeclsCtx&);
// =====
// 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("data"sv)) {
return ctx.makeData();
}
if (ctx.in.takeKeyword("array"sv)) {
return ctx.in.err("array heap type not yet supported");
}
auto type = typeidx(ctx);
CHECK_ERR(type);
return *type;
}
// reftype ::= 'funcref' => funcref
// | 'externref' => externref
// | 'anyref' => anyref
// | 'eqref' => eqref
// | 'i31ref' => i31ref
// | 'dataref' => dataref
// | '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("dataref"sv)) {
return ctx.makeRefType(ctx.makeData(), 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
template<typename Ctx> Result<typename Ctx::MemTypeT> memtype(Ctx& ctx) {
if (ctx.in.takeKeyword("i64"sv)) {
auto limits = limits64(ctx);
CHECK_ERR(limits);
return ctx.makeMemType64(*limits);
}
ctx.in.takeKeyword("i32"sv);
auto limits = limits32(ctx);
CHECK_ERR(limits);
return ctx.makeMemType32(*limits);
}
// 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
// ============
template<typename Ctx> MaybeResult<typename Ctx::InstrT> instr(Ctx& ctx) {
auto pos = ctx.in.getPos();
auto keyword = ctx.in.takeKeyword();
if (!keyword) {
return {};
}
auto op = *keyword;
#define NEW_INSTRUCTION_PARSER
#define NEW_WAT_PARSER
#include <gen-s-parser.inc>
}
template<typename Ctx> Result<typename Ctx::InstrsT> instrs(Ctx& ctx) {
auto insts = ctx.makeInstrs();
while (true) {
if (ctx.in.takeLParen()) {
// 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 (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 = instr(ctx)) {
CHECK_ERR(inst);
ctx.appendInstr(insts, *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");
}
}
continue;
}
// A non-folded instruction.
if (auto inst = instr(ctx)) {
CHECK_ERR(inst);
ctx.appendInstr(insts, *inst);
} else {
break;
}
}
return ctx.finishInstrs(insts);
}
template<typename Ctx> Result<typename Ctx::ExprT> expr(Ctx& ctx) {
auto insts = instrs(ctx);
CHECK_ERR(insts);
return ctx.makeExpr(*insts);
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeUnreachable(Ctx& ctx, Index pos) {
return ctx.makeUnreachable(pos);
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeNop(Ctx& ctx, Index pos) {
return ctx.makeNop(pos);
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeBinary(Ctx& ctx, Index pos, BinaryOp op) {
return ctx.makeBinary(pos, op);
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeUnary(Ctx& ctx, Index pos, UnaryOp op) {
return ctx.makeUnary(pos, op);
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeSelect(Ctx& ctx, Index pos) {
auto res = results(ctx);
CHECK_ERR(res);
return ctx.makeSelect(pos, res.getPtr());
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeDrop(Ctx& ctx, Index pos) {
return ctx.makeDrop(pos);
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeMemorySize(Ctx& ctx, Index pos) {
auto mem = maybeMemidx(ctx);
CHECK_ERR(mem);
return ctx.makeMemorySize(pos, mem.getPtr());
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeMemoryGrow(Ctx& ctx, Index pos) {
auto mem = maybeMemidx(ctx);
CHECK_ERR(mem);
return ctx.makeMemoryGrow(pos, mem.getPtr());
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeLocalGet(Ctx& ctx, Index pos) {
auto local = localidx(ctx);
CHECK_ERR(local);
return ctx.makeLocalGet(pos, *local);
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeLocalTee(Ctx& ctx, Index pos) {
auto local = localidx(ctx);
CHECK_ERR(local);
return ctx.makeLocalTee(pos, *local);
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeLocalSet(Ctx& ctx, Index pos) {
auto local = localidx(ctx);
CHECK_ERR(local);
return ctx.makeLocalSet(pos, *local);
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeGlobalGet(Ctx& ctx, Index pos) {
auto global = globalidx(ctx);
CHECK_ERR(global);
return ctx.makeGlobalGet(pos, *global);
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeGlobalSet(Ctx& ctx, Index pos) {
auto global = globalidx(ctx);
CHECK_ERR(global);
return ctx.makeGlobalSet(pos, *global);
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeBlock(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeThenOrElse(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> 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<typename Ctx::InstrT>
makeLoad(Ctx& ctx, Index pos, Type type, bool isAtomic) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT>
makeStore(Ctx& ctx, Index pos, Type type, bool isAtomic) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT>
makeAtomicRMWOrCmpxchg(Ctx& ctx, Index pos, Type type) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeAtomicRMW(
Ctx& ctx, Index pos, Type type, uint8_t bytes, const char* extra) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeAtomicCmpxchg(
Ctx& ctx, Index pos, Type type, uint8_t bytes, const char* extra) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeAtomicWait(Ctx& ctx, Index pos, Type type) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeAtomicNotify(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeAtomicFence(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT>
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<typename Ctx::InstrT>
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<typename Ctx::InstrT> 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<typename Ctx::InstrT>
makeSIMDTernary(Ctx& ctx, Index pos, SIMDTernaryOp op) {
return ctx.makeSIMDTernary(pos, op);
}
template<typename Ctx>
Result<typename Ctx::InstrT>
makeSIMDShift(Ctx& ctx, Index pos, SIMDShiftOp op) {
return ctx.makeSIMDShift(pos, op);
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeSIMDLoad(Ctx& ctx, Index pos, SIMDLoadOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT>
makeSIMDLoadStoreLane(Ctx& ctx, Index pos, SIMDLoadStoreLaneOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeMemoryInit(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeDataDrop(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeMemoryCopy(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeMemoryFill(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makePop(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeIf(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT>
makeMaybeBlock(Ctx& ctx, Index pos, size_t i, Type type) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeLoop(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeCall(Ctx& ctx, Index pos, bool isReturn) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT>
makeCallIndirect(Ctx& ctx, Index pos, bool isReturn) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeBreak(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeBreakTable(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeReturn(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefNull(Ctx& ctx, Index pos) {
auto t = heaptype(ctx);
CHECK_ERR(t);
return ctx.makeRefNull(pos, *t);
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefIs(Ctx& ctx, Index pos, RefIsOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefFunc(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefEq(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeTableGet(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeTableSet(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeTableSize(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeTableGrow(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeTry(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT>
makeTryOrCatchBody(Ctx& ctx, Index pos, Type type, bool isTry) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeThrow(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeRethrow(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeTupleMake(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeTupleExtract(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeCallRef(Ctx& ctx, Index pos, bool isReturn) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeI31New(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeI31Get(Ctx& ctx, Index pos, bool signed_) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefTest(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefTestStatic(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefCast(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefCastStatic(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefCastNopStatic(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeBrOn(Ctx& ctx, Index pos, BrOnOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeBrOnStatic(Ctx& ctx, Index pos, BrOnOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT>
makeStructNewStatic(Ctx& ctx, Index pos, bool default_) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeStructGet(Ctx& ctx, Index pos, bool signed_) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeStructSet(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT>
makeArrayNewStatic(Ctx& ctx, Index pos, bool default_) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeArrayInitStatic(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeArrayGet(Ctx& ctx, Index pos, bool signed_) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeArraySet(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeArrayLen(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeArrayCopy(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefAs(Ctx& ctx, Index pos, RefAsOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT>
makeStringNew(Ctx& ctx, Index pos, StringNewOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringConst(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT>
makeStringMeasure(Ctx& ctx, Index pos, StringMeasureOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT>
makeStringEncode(Ctx& ctx, Index pos, StringEncodeOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringConcat(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringEq(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringAs(Ctx& ctx, Index pos, StringAsOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringWTF8Advance(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringWTF16Get(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> makeStringIterNext(Ctx& ctx, Index pos) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT>
makeStringIterMove(Ctx& ctx, Index pos, StringIterMoveOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT>
makeStringSliceWTF(Ctx& ctx, Index pos, StringSliceWTFOp op) {
return ctx.in.err("unimplemented instruction");
}
template<typename Ctx>
Result<typename Ctx::InstrT> 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");
}
// memidx ::= x:u32 => x
// | v:id => x (if memories[x] = v)
template<typename Ctx>
MaybeResult<typename Ctx::MemoryT> 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::MemoryT> memidx(Ctx& ctx) {
if (auto idx = maybeMemidx(ctx)) {
CHECK_ERR(idx);
return *idx;
}
return ctx.in.err("expected memory index or identifier");
}
// globalidx ::= x:u32 => x
// | v:id => x (if globals[x] = v)
template<typename Ctx> Result<typename Ctx::GlobalT> 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");
}
// localidx ::= x:u32 => x
// | v:id => x (if locals[x] = v)
template<typename Ctx> Result<typename Ctx::LocalT> 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");
}
// 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 (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;
}
}
std::optional<typename Ctx::InstrsT> insts;
if (!import) {
auto i = instrs(ctx);
CHECK_ERR(i);
insts = *i;
}
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of function");
}
// TODO: Use `pos` instead of `in` for error position.
CHECK_ERR(
ctx.addFunc(name, *exports, import.getPtr(), *type, localVars, insts, 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;
if (ctx.in.takeSExprStart("data"sv)) {
if (import) {
return ctx.in.err("imported memories cannot have inline data");
}
auto data = datastring(ctx);
CHECK_ERR(data);
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of inline data");
}
mtype = ctx.makeMemType32(ctx.getLimitsFromData(*data));
// TODO: addDataSegment as well.
} 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));
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;
}
// modulefield ::= deftype
// | import
// | func
// | table
// | memory
// | global
// | export
// | start
// | elem
// | data
MaybeResult<> modulefield(ParseDeclsCtx& 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{};
}
return ctx.in.err("unrecognized module field");
}
// module ::= '(' 'module' id? (m:modulefield)* ')'
// | (m:modulefield)* eof
Result<> module(ParseDeclsCtx& 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{};
}
} // anonymous namespace
Result<> parseModule(Module& wasm, std::string_view input) {
// Parse module-level declarations.
ParseDeclsCtx decls(input, wasm);
CHECK_ERR(module(decls));
if (!decls.in.empty()) {
return decls.in.err("Unexpected tokens after module");
}
auto typeIndices = createIndexMap(decls.in, decls.subtypeDefs);
CHECK_ERR(typeIndices);
// Parse type definitions.
std::vector<HeapType> types;
{
TypeBuilder builder(decls.subtypeDefs.size());
ParseTypeDefsCtx ctx(input, builder, *typeIndices);
for (auto& typeDef : decls.typeDefs) {
WithPosition with(ctx, typeDef.pos);
CHECK_ERR(deftype(ctx));
}
auto built = builder.build();
if (auto* err = built.getError()) {
std::stringstream msg;
msg << "invalid type: " << err->reason;
return ctx.in.err(decls.typeDefs[err->index].pos, msg.str());
}
types = *built;
// Record type names on the module.
for (size_t i = 0; i < types.size(); ++i) {
auto& names = ctx.names[i];
if (names.name.is() || names.fieldNames.size()) {
wasm.typeNames.insert({types[i], names});
}
}
}
// Parse implicit type definitions and map typeuses without explicit types to
// the correct types.
std::unordered_map<Index, HeapType> implicitTypes;
{
ParseImplicitTypeDefsCtx ctx(input, types, implicitTypes, *typeIndices);
for (Index pos : decls.implicitTypeDefs) {
WithPosition with(ctx, pos);
CHECK_ERR(typeuse(ctx));
}
}
{
// Parse module-level types.
ParseModuleTypesCtx ctx(input, wasm, types, implicitTypes, *typeIndices);
CHECK_ERR(parseDefs(ctx, decls.funcDefs, func));
CHECK_ERR(parseDefs(ctx, decls.memoryDefs, memory));
CHECK_ERR(parseDefs(ctx, decls.globalDefs, global));
// TODO: Parse types of other module elements.
}
{
// Parse definitions.
// TODO: Parallelize this.
ParseDefsCtx ctx(input, wasm, types, implicitTypes, *typeIndices);
CHECK_ERR(parseDefs(ctx, decls.globalDefs, global));
for (Index i = 0; i < decls.funcDefs.size(); ++i) {
ctx.index = i;
ctx.func = wasm.functions[i].get();
ctx.type = ctx.func->getResults();
WithPosition with(ctx, decls.funcDefs[i].pos);
auto parsed = func(ctx);
CHECK_ERR(parsed);
assert(parsed);
}
}
return Ok{};
}
} // namespace wasm::WATParser