blob: d486ef13e4195199150e4cab964abb34ee2b97bd [file] [log] [blame]
/*
* Copyright 2015 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 "wasm-s-parser.h"
#include <cctype>
#include <cmath>
#include <limits>
#include "ir/branch-utils.h"
#include "ir/table-utils.h"
#include "shared-constants.h"
#include "support/string.h"
#include "wasm-binary.h"
#include "wasm-builder.h"
using namespace std::string_literals;
#define abort_on(str) \
{ throw ParseException(std::string("abort_on ") + str); }
#define element_assert(condition) \
assert((condition) ? true : (std::cerr << "on: " << *this << '\n' && 0));
namespace {
int unhex(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
}
if (c >= 'A' && c <= 'F') {
return c - 'A' + 10;
}
throw wasm::ParseException("invalid hexadecimal");
}
} // namespace
namespace wasm {
static Name STRUCT("struct"), FIELD("field"), ARRAY("array"), REC("rec"),
I8("i8"), I16("i16"), DECLARE("declare"), ITEM("item"), OFFSET("offset"),
SUB("sub"), FINAL("final");
static Address getAddress(const Element* s) {
return std::stoll(s->toString());
}
static void
checkAddress(Address a, const char* errorText, const Element* errorElem) {
if (a > std::numeric_limits<Address::address32_t>::max()) {
throw ParseException(errorText, errorElem->line, errorElem->col);
}
}
static bool elementStartsWith(Element& s, IString str) {
return s.isList() && s.size() > 0 && s[0]->isStr() && s[0]->str() == str;
}
static bool elementStartsWith(Element* s, IString str) {
return elementStartsWith(*s, str);
}
Element::List& Element::list() {
if (!isList()) {
throw ParseException("expected list, found "s + toString(), line, col);
}
return list_;
}
Element* Element::operator[](unsigned i) {
if (!isList()) {
throw ParseException("expected list, found "s + toString(), line, col);
}
if (i >= list().size()) {
throw ParseException(
"expected more elements in list "s + toString(), line, col);
}
return list()[i];
}
IString Element::str() const {
if (!isStr()) {
throw ParseException(
"expected string, found list "s + toString(), line, col);
}
return str_;
}
Element* Element::setString(IString str__, bool dollared__, bool quoted__) {
isList_ = false;
str_ = str__;
dollared_ = dollared__;
quoted_ = quoted__;
return this;
}
Element*
Element::setMetadata(size_t line_, size_t col_, SourceLocation* startLoc_) {
line = line_;
col = col_;
startLoc = startLoc_;
return this;
}
std::ostream& operator<<(std::ostream& o, const Element& e) {
if (e.isList_) {
o << '(';
for (auto item : e.list_) {
o << ' ' << *item;
}
o << " )";
} else {
if (e.dollared()) {
o << '$';
}
o << e.str_.str;
}
return o;
}
std::string Element::toString() const {
std::stringstream ss;
ss << *this;
return ss.str();
}
void Element::dump() {
std::cout << "dumping " << this << " : " << *this << ".\n";
}
SExpressionParser::SExpressionParser(char const* input) : input(input) {
root = nullptr;
line = 1;
lineStart = input;
while (!root) { // keep parsing until we pass an initial comment
root = parse();
}
}
Element* SExpressionParser::parse() {
std::vector<Element*> stack;
std::vector<SourceLocation*> stackLocs;
Element* curr = allocator.alloc<Element>();
while (1) {
skipWhitespace();
if (input[0] == 0) {
break;
}
if (input[0] == '(') {
input++;
stack.push_back(curr);
curr = allocator.alloc<Element>()->setMetadata(
line, input - lineStart - 1, loc);
stackLocs.push_back(loc);
assert(stack.size() == stackLocs.size());
} else if (input[0] == ')') {
input++;
curr->endLoc = loc;
auto last = curr;
if (stack.empty()) {
throw ParseException("s-expr stack empty");
}
curr = stack.back();
assert(stack.size() == stackLocs.size());
stack.pop_back();
loc = stackLocs.back();
stackLocs.pop_back();
curr->list().push_back(last);
} else {
curr->list().push_back(parseString());
}
}
if (stack.size() != 0) {
throw ParseException("stack is not empty", curr->line, curr->col);
}
return curr;
}
void SExpressionParser::parseDebugLocation() {
// Extracting debug location (if valid)
char const* debugLoc = input + 3; // skipping ";;@"
while (debugLoc[0] && debugLoc[0] == ' ') {
debugLoc++;
}
char const* debugLocEnd = debugLoc;
while (debugLocEnd[0] && debugLocEnd[0] != '\n') {
debugLocEnd++;
}
char const* pos = debugLoc;
while (pos < debugLocEnd && pos[0] != ':') {
pos++;
}
if (pos >= debugLocEnd) {
return; // no line number
}
std::string name(debugLoc, pos);
char const* lineStart = ++pos;
while (pos < debugLocEnd && pos[0] != ':') {
pos++;
}
std::string lineStr(lineStart, pos);
if (pos >= debugLocEnd) {
return; // no column number
}
std::string colStr(++pos, debugLocEnd);
void* buf =
allocator.allocSpace(sizeof(SourceLocation), alignof(SourceLocation));
loc = new (buf) SourceLocation(
IString(name.c_str(), false), atoi(lineStr.c_str()), atoi(colStr.c_str()));
}
void SExpressionParser::skipWhitespace() {
while (1) {
while (isspace(input[0])) {
if (input[0] == '\n') {
line++;
lineStart = input + 1;
}
input++;
}
if (input[0] == ';' && input[1] == ';') {
if (input[2] == '@') {
parseDebugLocation();
}
while (input[0] && input[0] != '\n') {
input++;
}
line++;
if (!input[0]) {
return;
}
lineStart = ++input;
} else if (input[0] == '(' && input[1] == ';') {
// Skip nested block comments.
input += 2;
int depth = 1;
while (1) {
if (!input[0]) {
return;
}
if (input[0] == '(' && input[1] == ';') {
input += 2;
depth++;
} else if (input[0] == ';' && input[1] == ')') {
input += 2;
--depth;
if (depth == 0) {
break;
}
} else if (input[0] == '\n') {
line++;
lineStart = input;
input++;
} else {
input++;
}
}
} else {
return;
}
}
}
Element* SExpressionParser::parseString() {
bool dollared = false;
if (input[0] == '$') {
input++;
dollared = true;
}
char const* start = input;
if (input[0] == '"') {
// parse escaping \", but leave code escaped - we'll handle escaping in
// memory segments specifically
input++;
std::string str;
while (1) {
if (input[0] == 0) {
throw ParseException("unterminated string", line, start - lineStart);
}
if (input[0] == '"') {
break;
}
if (input[0] == '\\') {
str += input[0];
if (input[1] == 0) {
throw ParseException(
"unterminated string escape", line, start - lineStart);
}
str += input[1];
input += 2;
continue;
}
str += input[0];
input++;
}
input++;
return allocator.alloc<Element>()
->setString(IString(str.c_str(), false), dollared, true)
->setMetadata(line, start - lineStart, loc);
}
while (input[0] && !isspace(input[0]) && input[0] != ')' && input[0] != '(' &&
input[0] != ';') {
input++;
}
if (start == input) {
throw ParseException("expected string", line, input - lineStart);
}
std::string temp;
temp.assign(start, input - start);
auto ret = allocator.alloc<Element>()
->setString(IString(temp.c_str(), false), dollared, false)
->setMetadata(line, start - lineStart, loc);
return ret;
}
SExpressionWasmBuilder::SExpressionWasmBuilder(Module& wasm,
Element& module,
IRProfile profile)
: wasm(wasm), allocator(wasm.allocator), profile(profile) {
if (module.size() == 0) {
throw ParseException("empty toplevel, expected module");
}
if (module[0]->str() != MODULE) {
throw ParseException("toplevel does not start with module");
}
if (module.size() == 1) {
return;
}
Index i = 1;
if (module[i]->dollared()) {
wasm.name = module[i]->str();
if (module.size() == 2) {
return;
}
i++;
}
// spec tests have a `binary` keyword after the optional module name. Skip it
Name BINARY("binary");
if (module[i]->isStr() && module[i]->str() == BINARY &&
!module[i]->quoted()) {
i++;
}
if (i < module.size() && module[i]->isStr()) {
// these s-expressions contain a binary module, actually
std::vector<char> data;
for (; i < module.size(); ++i) {
stringToBinary(*module[i], module[i]->str().str, data);
}
// TODO: support applying features here
WasmBinaryReader binaryBuilder(wasm, FeatureSet::MVP, data);
binaryBuilder.read();
return;
}
preParseHeapTypes(module);
Index implementedFunctions = 0;
functionCounter = 0;
for (unsigned j = i; j < module.size(); j++) {
auto& s = *module[j];
preParseFunctionType(s);
preParseImports(s);
preParseMemory(s);
if (elementStartsWith(s, FUNC) && !isImport(s)) {
implementedFunctions++;
}
}
// we go through the functions again, now parsing them, and the counter begins
// from where imports ended
functionCounter -= implementedFunctions;
for (unsigned j = i; j < module.size(); j++) {
parseModuleElement(*module[j]);
}
}
bool SExpressionWasmBuilder::isImport(Element& curr) {
for (Index i = 0; i < curr.size(); i++) {
auto& x = *curr[i];
if (elementStartsWith(x, IMPORT)) {
return true;
}
}
return false;
}
void SExpressionWasmBuilder::preParseImports(Element& curr) {
IString id = curr[0]->str();
if (id == IMPORT) {
parseImport(curr);
}
if (isImport(curr)) {
if (id == FUNC) {
parseFunction(curr, true /* preParseImport */);
} else if (id == GLOBAL) {
parseGlobal(curr, true /* preParseImport */);
} else if (id == TABLE) {
parseTable(curr, true /* preParseImport */);
} else if (id == MEMORY) {
parseMemory(curr, true /* preParseImport */);
} else if (id == TAG) {
parseTag(curr, true /* preParseImport */);
} else {
throw ParseException("fancy import we don't support yet: "s +
curr.toString(),
curr.line,
curr.col);
}
}
}
void SExpressionWasmBuilder::preParseMemory(Element& curr) {
IString id = curr[0]->str();
if (id == MEMORY && !isImport(curr)) {
parseMemory(curr);
}
}
void SExpressionWasmBuilder::parseModuleElement(Element& curr) {
if (isImport(curr)) {
return; // already done
}
IString id = curr[0]->str();
if (id == MEMORY) {
return; // already done
}
if (id == START) {
return parseStart(curr);
}
if (id == FUNC) {
return parseFunction(curr);
}
if (id == DATA) {
return parseData(curr);
}
if (id == EXPORT) {
return parseExport(curr);
}
if (id == IMPORT) {
return; // already done
}
if (id == GLOBAL) {
return parseGlobal(curr);
}
if (id == TABLE) {
return parseTable(curr);
}
if (id == ELEM) {
return parseElem(curr);
}
if (id == TYPE) {
return; // already done
}
if (id == REC) {
return; // already done
}
if (id == TAG) {
return parseTag(curr);
}
std::cerr << "bad module element " << id.str << '\n';
throw ParseException(
"unknown module element: "s + curr.toString(), curr.line, curr.col);
}
int SExpressionWasmBuilder::parseIndex(Element& s) {
try {
return std::stoi(s.toString());
} catch (...) {
throw ParseException(
"expected integer, found "s + s.toString(), s.line, s.col);
}
}
Name SExpressionWasmBuilder::getFunctionName(Element& s) {
if (s.dollared()) {
return s.str();
} else {
// index
size_t offset = parseIndex(s);
if (offset >= functionNames.size()) {
throw ParseException(
"unknown function in getFunctionName: "s + s.toString(), s.line, s.col);
}
return functionNames[offset];
}
}
Name SExpressionWasmBuilder::getTableName(Element& s) {
if (s.dollared()) {
return s.str();
} else {
// index
size_t offset = parseIndex(s);
if (offset >= tableNames.size()) {
throw ParseException(
"unknown table in getTableName: "s + s.toString(), s.line, s.col);
}
return tableNames[offset];
}
}
Name SExpressionWasmBuilder::getElemSegmentName(Element& s) {
if (s.dollared()) {
return s.str();
} else {
// index
size_t offset = parseIndex(s);
if (offset >= elemSegmentNames.size()) {
throw ParseException(
"unknown elem segment: "s + s.toString(), s.line, s.col);
}
return elemSegmentNames[offset];
}
}
bool SExpressionWasmBuilder::isMemory64(Name memoryName) {
auto* memory = wasm.getMemoryOrNull(memoryName);
if (!memory) {
throw ParseException("invalid memory name in isMemory64: "s +
memoryName.toString());
}
return memory->is64();
}
Name SExpressionWasmBuilder::getMemoryNameAtIdx(Index i) {
if (i >= memoryNames.size()) {
throw ParseException("unknown memory in getMemoryName: "s +
std::to_string(i));
}
return memoryNames[i];
}
Name SExpressionWasmBuilder::getMemoryName(Element& s) {
if (s.dollared()) {
return s.str();
} else {
// index
size_t offset = parseIndex(s);
return getMemoryNameAtIdx(offset);
}
}
Name SExpressionWasmBuilder::getDataSegmentName(Element& s) {
if (s.dollared()) {
return s.str();
} else {
// index
size_t offset = parseIndex(s);
if (offset >= dataSegmentNames.size()) {
throw ParseException(
"unknown data segment: "s + s.toString(), s.line, s.col);
}
return dataSegmentNames[offset];
}
}
Name SExpressionWasmBuilder::getGlobalName(Element& s) {
if (s.dollared()) {
return s.str();
} else {
// index
size_t offset = parseIndex(s);
if (offset >= globalNames.size()) {
throw ParseException(
"unknown global in getGlobalName: "s + s.toString(), s.line, s.col);
}
return globalNames[offset];
}
}
Name SExpressionWasmBuilder::getTagName(Element& s) {
if (s.dollared()) {
return s.str();
} else {
// index
size_t offset = parseIndex(s);
if (offset >= tagNames.size()) {
throw ParseException(
"unknown tag in getTagName: "s + s.toString(), s.line, s.col);
}
return tagNames[offset];
}
}
// Parse various forms of (param ...) or (local ...) element. This ignores all
// parameter or local names when specified.
std::vector<Type> SExpressionWasmBuilder::parseParamOrLocal(Element& s) {
size_t fakeIndex = 0;
std::vector<NameType> namedParams = parseParamOrLocal(s, fakeIndex);
std::vector<Type> params;
for (auto& p : namedParams) {
params.push_back(p.type);
}
return params;
}
// Parses various forms of (param ...) or (local ...) element:
// (param $name type) (e.g. (param $a i32))
// (param type+) (e.g. (param i32 f64))
// (local $name type) (e.g. (local $a i32))
// (local type+) (e.g. (local i32 f64))
// If the name is unspecified, it will create one using localIndex.
std::vector<NameType>
SExpressionWasmBuilder::parseParamOrLocal(Element& s, size_t& localIndex) {
assert(elementStartsWith(s, PARAM) || elementStartsWith(s, LOCAL));
std::vector<NameType> namedParams;
if (s.size() == 1) { // (param) or (local)
return namedParams;
}
for (size_t i = 1; i < s.size(); i++) {
IString name;
if (s[i]->dollared()) {
if (i != 1) {
throw ParseException(
"invalid wasm type: "s + s.toString(), s[i]->line, s[i]->col);
}
if (i + 1 >= s.size()) {
throw ParseException(
"invalid param entry: "s + s.toString(), s.line, s.col);
}
name = s[i]->str();
i++;
} else {
name = Name::fromInt(localIndex);
}
localIndex++;
Type type;
type = elementToType(*s[i]);
if (elementStartsWith(s, PARAM) && type.isTuple()) {
throw ParseException("params may not have tuple types: "s + s.toString(),
s[i]->line,
s[i]->col);
}
namedParams.emplace_back(name, type);
}
return namedParams;
}
// Parses (result type) element. (e.g. (result i32))
std::vector<Type> SExpressionWasmBuilder::parseResults(Element& s) {
assert(elementStartsWith(s, RESULT));
std::vector<Type> types;
for (size_t i = 1; i < s.size(); i++) {
types.push_back(elementToType(*s[i]));
}
return types;
}
// Parses an element that references an entry in the type section. The element
// should be in the form of (type name) or (type index).
// (e.g. (type $a), (type 0))
HeapType SExpressionWasmBuilder::parseTypeRef(Element& s) {
assert(elementStartsWith(s, TYPE));
if (s.size() != 2) {
throw ParseException(
"invalid type reference: "s + s.toString(), s.line, s.col);
}
auto heapType = parseHeapType(*s[1]);
if (!heapType.isSignature()) {
throw ParseException(
"expected signature type: "s + s.toString(), s.line, s.col);
}
return heapType;
}
// Parses typeuse, a reference to a type definition. It is in the form of either
// (type index) or (type name), possibly augmented by inlined (param) and
// (result) nodes. (type) node can be omitted as well. Outputs are returned by
// parameter references.
// typeuse ::= (type index|name)+ |
// (type index|name)+ (param ..)* (result ..)* |
// (param ..)* (result ..)*
size_t
SExpressionWasmBuilder::parseTypeUse(Element& s,
size_t startPos,
HeapType& functionType,
std::vector<NameType>& namedParams) {
std::vector<Type> params, results;
size_t i = startPos;
bool typeExists = false, paramsOrResultsExist = false;
if (i < s.size() && elementStartsWith(*s[i], TYPE)) {
typeExists = true;
functionType = parseTypeRef(*s[i++]);
}
size_t paramPos = i;
size_t localIndex = 0;
while (i < s.size() && elementStartsWith(*s[i], PARAM)) {
paramsOrResultsExist = true;
auto newParams = parseParamOrLocal(*s[i++], localIndex);
namedParams.insert(namedParams.end(), newParams.begin(), newParams.end());
for (auto p : newParams) {
params.push_back(p.type);
}
}
while (i < s.size() && elementStartsWith(*s[i], RESULT)) {
paramsOrResultsExist = true;
auto newResults = parseResults(*s[i++]);
results.insert(results.end(), newResults.begin(), newResults.end());
}
auto inlineSig = Signature(Type(params), Type(results));
// If none of type/param/result exists, this is equivalent to a type that does
// not have parameters and returns nothing.
if (!typeExists && !paramsOrResultsExist) {
paramsOrResultsExist = true;
}
if (!typeExists) {
functionType = inlineSig;
} else if (paramsOrResultsExist) {
// verify that (type) and (params)/(result) match
if (inlineSig != functionType.getSignature()) {
throw ParseException("type and param/result don't match: "s +
s.toString(),
s[paramPos]->line,
s[paramPos]->col);
}
}
// Add implicitly defined type to global list so it has an index
if (std::find(types.begin(), types.end(), functionType) == types.end()) {
types.push_back(functionType);
}
// If only (type) is specified, populate `namedParams`
if (!paramsOrResultsExist) {
size_t index = 0;
assert(functionType.isSignature());
Signature sig = functionType.getSignature();
for (const auto& param : sig.params) {
namedParams.emplace_back(Name::fromInt(index++), param);
}
}
return i;
}
// Parses a typeuse. Use this when only FunctionType* is needed.
size_t SExpressionWasmBuilder::parseTypeUse(Element& s,
size_t startPos,
HeapType& functionType) {
std::vector<NameType> params;
return parseTypeUse(s, startPos, functionType, params);
}
void SExpressionWasmBuilder::preParseHeapTypes(Element& module) {
// Iterate through each individual type definition, calling `f` with the
// definition and its recursion group number.
auto forEachType = [&](auto f) {
size_t groupNumber = 0;
for (auto* elemPtr : module) {
auto& elem = *elemPtr;
if (elementStartsWith(elem, TYPE)) {
f(elem, groupNumber++);
} else if (elementStartsWith(elem, REC)) {
for (auto* innerPtr : elem) {
auto& inner = *innerPtr;
if (elementStartsWith(inner, TYPE)) {
f(inner, groupNumber);
}
}
++groupNumber;
}
}
};
// Map type names to indices
size_t numTypes = 0;
forEachType([&](Element& elem, size_t) {
if (elem[1]->dollared()) {
std::string name = elem[1]->str().toString();
if (!typeIndices.insert({name, numTypes}).second) {
throw ParseException(
"duplicate function type: "s + elem.toString(), elem.line, elem.col);
}
}
++numTypes;
});
TypeBuilder builder(numTypes);
// Create recursion groups
size_t currGroup = 0, groupStart = 0, groupLength = 0;
auto finishGroup = [&]() {
builder.createRecGroup(groupStart, groupLength);
groupStart = groupStart + groupLength;
groupLength = 0;
};
forEachType([&](Element&, size_t group) {
if (group != currGroup) {
finishGroup();
currGroup = group;
}
++groupLength;
});
finishGroup();
auto parseRefType = [&](Element& elem) -> Type {
// '(' 'ref' 'null'? ht ')'
auto nullable =
elem[1]->isStr() && *elem[1] == NULL_ ? Nullable : NonNullable;
auto& referent = nullable ? *elem[2] : *elem[1];
auto name = referent.str().toString();
if (referent.dollared()) {
return builder.getTempRefType(builder[typeIndices[name]], nullable);
} else if (String::isNumber(name)) {
size_t index = parseIndex(referent);
if (index >= numTypes) {
throw ParseException(
"invalid type index: "s + std::to_string(index), elem.line, elem.col);
}
return builder.getTempRefType(builder[index], nullable);
} else {
return Type(stringToHeapType(referent.str()), nullable);
}
};
auto parseValType = [&](Element& elem) {
if (elem.isStr()) {
return stringToType(elem.str());
} else if (*elem[0] == REF) {
return parseRefType(elem);
} else {
throw ParseException("unknown valtype kind: "s + elem.toString(),
elem[0]->line,
elem[0]->col);
}
};
auto parseParams = [&](Element& elem) {
auto it = ++elem.begin();
if (it != elem.end() && (*it)->dollared()) {
++it;
}
std::vector<Type> params;
for (auto end = elem.end(); it != end; ++it) {
params.push_back(parseValType(**it));
}
return params;
};
auto parseResults = [&](Element& elem) {
std::vector<Type> results;
for (auto it = ++elem.begin(); it != elem.end(); ++it) {
results.push_back(parseValType(**it));
}
return results;
};
auto parseSignatureDef = [&](Element& elem, bool nominal) {
// '(' 'func' vec(param) vec(result) ')'
// param ::= '(' 'param' id? valtype ')'
// result ::= '(' 'result' valtype ')'
std::vector<Type> params, results;
auto end = elem.end() - (nominal ? 1 : 0);
for (auto it = ++elem.begin(); it != end; ++it) {
Element& curr = **it;
if (elementStartsWith(curr, PARAM)) {
auto newParams = parseParams(curr);
params.insert(params.end(), newParams.begin(), newParams.end());
} else if (elementStartsWith(curr, RESULT)) {
auto newResults = parseResults(curr);
results.insert(results.end(), newResults.begin(), newResults.end());
}
}
return Signature(builder.getTempTupleType(params),
builder.getTempTupleType(results));
};
// Parses a field, and notes the name if one is found.
auto parseField = [&](Element* elem, Name& name) {
Mutability mutable_ = Immutable;
// elem is a list, containing either
// TYPE
// or
// (field TYPE)
// or
// (field $name TYPE)
if (elementStartsWith(elem, FIELD)) {
if (elem->size() == 3) {
name = (*elem)[1]->str();
}
elem = (*elem)[elem->size() - 1];
}
// The element may also be (mut (..)).
if (elementStartsWith(elem, MUT)) {
mutable_ = Mutable;
elem = (*elem)[1];
}
if (elem->isStr()) {
// elem is a simple string name like "i32". It can be a normal wasm
// type, or one of the special types only available in fields.
if (*elem == I8) {
return Field(Field::i8, mutable_);
} else if (*elem == I16) {
return Field(Field::i16, mutable_);
}
}
// Otherwise it's an arbitrary type.
return Field(parseValType(*elem), mutable_);
};
auto parseStructDef = [&](Element& elem, size_t typeIndex, bool nominal) {
FieldList fields;
Index end = elem.size() - (nominal ? 1 : 0);
for (Index i = 1; i < end; i++) {
Name name;
fields.emplace_back(parseField(elem[i], name));
if (name.is()) {
// Only add the name to the map if it exists.
fieldNames[typeIndex][i - 1] = name;
}
}
return Struct(fields);
};
auto parseArrayDef = [&](Element& elem) {
Name unused;
return Array(parseField(elem[1], unused));
};
size_t index = 0;
forEachType([&](Element& elem, size_t) {
Element& def = elem[1]->dollared() ? *elem[2] : *elem[1];
Element& kind = *def[0];
Element* super = nullptr;
if (kind == SUB) {
Index i = 1;
if (*def[i] == FINAL) {
++i;
} else {
builder[index].setOpen();
}
if (def[i]->dollared()) {
super = def[i];
++i;
}
Element& subtype = *def[i++];
if (i != def.size()) {
throw ParseException(
"invalid 'sub' form: "s + elem.toString(), kind.line, kind.col);
}
if (!subtype.isList() || subtype.size() < 1) {
throw ParseException("invalid subtype definition: "s + elem.toString(),
subtype.line,
subtype.col);
}
Element& subtypeKind = *subtype[0];
if (subtypeKind == FUNC) {
builder[index] = parseSignatureDef(subtype, 0);
} else if (subtypeKind == STRUCT) {
builder[index] = parseStructDef(subtype, index, 0);
} else if (subtypeKind == ARRAY) {
builder[index] = parseArrayDef(subtype);
} else {
throw ParseException("unknown subtype kind: "s + elem.toString(),
subtypeKind.line,
subtypeKind.col);
}
} else {
if (kind == FUNC) {
builder[index] = parseSignatureDef(def, 0);
} else if (kind == STRUCT) {
builder[index] = parseStructDef(def, index, 0);
} else if (kind == ARRAY) {
builder[index] = parseArrayDef(def);
} else {
throw ParseException(
"unknown heaptype kind: "s + elem.toString(), kind.line, kind.col);
}
}
if (super) {
auto it = typeIndices.find(super->str().toString());
if (!super->dollared() || it == typeIndices.end()) {
throw ParseException(
"unknown supertype: "s + elem.toString(), super->line, super->col);
}
builder[index].subTypeOf(builder[it->second]);
}
++index;
});
auto result = builder.build();
if (auto* err = result.getError()) {
// Find the name to provide a better error message.
std::stringstream msg;
msg << "Invalid type: " << err->reason;
for (auto& [name, index] : typeIndices) {
if (index == err->index) {
Fatal() << msg.str() << " at type $" << name;
}
}
// No name, just report the index.
Fatal() << msg.str() << " at index " << err->index;
}
types = *result;
for (auto& [name, index] : typeIndices) {
auto type = types[index];
// A type may appear in the type section more than once, but we canonicalize
// types internally, so there will be a single name chosen for that type. Do
// so determistically.
if (wasm.typeNames.count(type) && wasm.typeNames[type].name.str < name) {
continue;
}
auto& currTypeNames = wasm.typeNames[type];
currTypeNames.name = name;
if (type.isStruct()) {
currTypeNames.fieldNames = fieldNames[index];
}
}
}
void SExpressionWasmBuilder::preParseFunctionType(Element& s) {
IString id = s[0]->str();
if (id != FUNC) {
return;
}
size_t i = 1;
Name name, exportName;
i = parseFunctionNames(s, name, exportName);
if (!name.is()) {
// unnamed, use an index
name = Name::fromInt(functionCounter);
}
functionNames.push_back(name);
functionCounter++;
parseTypeUse(s, i, functionTypes[name]);
}
size_t SExpressionWasmBuilder::parseFunctionNames(Element& s,
Name& name,
Name& exportName) {
size_t i = 1;
while (i < s.size() && i < 3 && s[i]->isStr()) {
if (s[i]->quoted()) {
// an export name
exportName = s[i]->str();
i++;
} else if (s[i]->dollared()) {
name = s[i]->str();
i++;
} else {
break;
}
}
if (i < s.size() && s[i]->isList()) {
auto& inner = *s[i];
if (elementStartsWith(inner, EXPORT)) {
exportName = inner[1]->str();
i++;
}
}
#if 0
if (exportName.is() && !name.is()) {
name = exportName; // useful for debugging
}
#endif
return i;
}
void SExpressionWasmBuilder::parseFunction(Element& s, bool preParseImport) {
brokeToAutoBlock = false;
Name name, exportName;
size_t i = parseFunctionNames(s, name, exportName);
bool hasExplicitName = name.is();
if (!preParseImport) {
if (!name.is()) {
// unnamed, use an index
name = Name::fromInt(functionCounter);
}
functionCounter++;
} else {
// just preparsing, functionCounter was incremented by preParseFunctionType
if (!name.is()) {
// unnamed, use an index
name = functionNames[functionCounter - 1];
}
}
if (exportName.is()) {
auto ex = std::make_unique<Export>();
ex->name = exportName;
ex->value = name;
ex->kind = ExternalKind::Function;
if (wasm.getExportOrNull(ex->name)) {
throw ParseException(
"duplicate export: "s + ex->name.toString(), s.line, s.col);
}
wasm.addExport(ex.release());
}
// parse import
Name importModule, importBase;
if (i < s.size() && elementStartsWith(*s[i], IMPORT)) {
Element& curr = *s[i];
importModule = curr[1]->str();
importBase = curr[2]->str();
i++;
}
// parse typeuse: type/param/result
HeapType type;
std::vector<NameType> params;
i = parseTypeUse(s, i, type, params);
// when (import) is inside a (func) element, this is not a function definition
// but an import.
if (importModule.is()) {
if (!importBase.size()) {
throw ParseException(
"module but no base for import: "s + s.toString(), s.line, s.col);
}
if (!preParseImport) {
throw ParseException(
"!preParseImport in func: "s + s.toString(), s.line, s.col);
}
auto im = std::make_unique<Function>();
im->setName(name, hasExplicitName);
im->module = importModule;
im->base = importBase;
im->type = type;
functionTypes[name] = type;
if (wasm.getFunctionOrNull(im->name)) {
throw ParseException(
"duplicate import: "s + im->name.toString(), s.line, s.col);
}
wasm.addFunction(std::move(im));
if (currFunction) {
throw ParseException(
"import module inside function dec: "s + s.toString(), s.line, s.col);
}
nameMapper.clear();
return;
}
// at this point this not an import but a real function definition.
if (preParseImport) {
throw ParseException(
"preParseImport in func: "s + s.toString(), s.line, s.col);
}
size_t localIndex = params.size(); // local index for params and locals
// parse locals
std::vector<NameType> vars;
while (i < s.size() && elementStartsWith(*s[i], LOCAL)) {
auto newVars = parseParamOrLocal(*s[i++], localIndex);
vars.insert(vars.end(), newVars.begin(), newVars.end());
}
// make a new function
currFunction = std::unique_ptr<Function>(
Builder(wasm).makeFunction(name, std::move(params), type, std::move(vars)));
currFunction->profile = profile;
// parse body
Block* autoBlock = nullptr; // may need to add a block for the very top level
auto ensureAutoBlock = [&]() {
if (!autoBlock) {
autoBlock = allocator.alloc<Block>();
autoBlock->list.push_back(currFunction->body);
currFunction->body = autoBlock;
}
};
while (i < s.size()) {
Expression* ex = parseExpression(*s[i++]);
if (!currFunction->body) {
currFunction->body = ex;
} else {
ensureAutoBlock();
autoBlock->list.push_back(ex);
}
}
if (brokeToAutoBlock) {
ensureAutoBlock();
autoBlock->name = FAKE_RETURN;
}
if (autoBlock) {
autoBlock->finalize(type.getSignature().results);
}
if (!currFunction->body) {
currFunction->body = allocator.alloc<Nop>();
}
if (s.startLoc) {
currFunction->prologLocation.insert(getDebugLocation(*s.startLoc));
}
if (s.endLoc) {
currFunction->epilogLocation.insert(getDebugLocation(*s.endLoc));
}
if (wasm.getFunctionOrNull(currFunction->name)) {
throw ParseException(
"duplicate function: "s + currFunction->name.toString(), s.line, s.col);
}
wasm.addFunction(currFunction.release());
nameMapper.clear();
}
Type SExpressionWasmBuilder::stringToType(std::string_view str,
bool allowError,
bool prefix) {
if (str.size() >= 3) {
if (str[0] == 'i') {
if (str[1] == '3' && str[2] == '2' && (prefix || str.size() == 3)) {
return Type::i32;
}
if (str[1] == '6' && str[2] == '4' && (prefix || str.size() == 3)) {
return Type::i64;
}
}
if (str[0] == 'f') {
if (str[1] == '3' && str[2] == '2' && (prefix || str.size() == 3)) {
return Type::f32;
}
if (str[1] == '6' && str[2] == '4' && (prefix || str.size() == 3)) {
return Type::f64;
}
}
}
if (str.size() >= 4) {
if (str[0] == 'v') {
if (str[1] == '1' && str[2] == '2' && str[3] == '8' &&
(prefix || str.size() == 4)) {
return Type::v128;
}
}
}
if (str.substr(0, 7) == "funcref" && (prefix || str.size() == 7)) {
return Type(HeapType::func, Nullable);
}
if (str.substr(0, 9) == "externref" && (prefix || str.size() == 9)) {
return Type(HeapType::ext, Nullable);
}
if (str.substr(0, 6) == "anyref" && (prefix || str.size() == 6)) {
return Type(HeapType::any, Nullable);
}
if (str.substr(0, 5) == "eqref" && (prefix || str.size() == 5)) {
return Type(HeapType::eq, Nullable);
}
if (str.substr(0, 6) == "i31ref" && (prefix || str.size() == 6)) {
return Type(HeapType::i31, Nullable);
}
if (str.substr(0, 9) == "structref" && (prefix || str.size() == 9)) {
return Type(HeapType::struct_, Nullable);
}
if (str.substr(0, 8) == "arrayref" && (prefix || str.size() == 8)) {
return Type(HeapType::array, Nullable);
}
if (str.substr(0, 9) == "stringref" && (prefix || str.size() == 9)) {
return Type(HeapType::string, Nullable);
}
if (str.substr(0, 15) == "stringview_wtf8" && (prefix || str.size() == 15)) {
return Type(HeapType::stringview_wtf8, Nullable);
}
if (str.substr(0, 16) == "stringview_wtf16" && (prefix || str.size() == 16)) {
return Type(HeapType::stringview_wtf16, Nullable);
}
if (str.substr(0, 15) == "stringview_iter" && (prefix || str.size() == 15)) {
return Type(HeapType::stringview_iter, Nullable);
}
if (str.substr(0, 7) == "nullref" && (prefix || str.size() == 7)) {
return Type(HeapType::none, Nullable);
}
if (str.substr(0, 13) == "nullexternref" && (prefix || str.size() == 13)) {
return Type(HeapType::noext, Nullable);
}
if (str.substr(0, 11) == "nullfuncref" && (prefix || str.size() == 11)) {
return Type(HeapType::nofunc, Nullable);
}
if (allowError) {
return Type::none;
}
throw ParseException("invalid wasm type: "s +
std::string(str.data(), str.size()));
}
HeapType SExpressionWasmBuilder::stringToHeapType(std::string_view str,
bool prefix) {
if (str.substr(0, 4) == "func" && (prefix || str.size() == 4)) {
return HeapType::func;
}
if (str.substr(0, 2) == "eq" && (prefix || str.size() == 2)) {
return HeapType::eq;
}
if (str.substr(0, 6) == "extern" && (prefix || str.size() == 6)) {
return HeapType::ext;
}
if (str.substr(0, 3) == "any" && (prefix || str.size() == 3)) {
return HeapType::any;
}
if (str.substr(0, 3) == "i31" && (prefix || str.size() == 3)) {
return HeapType::i31;
}
if (str.substr(0, 6) == "struct" && (prefix || str.size() == 6)) {
return HeapType::struct_;
}
if (str.substr(0, 5) == "array" && (prefix || str.size() == 5)) {
return HeapType::array;
}
if (str.substr(0, 6) == "string" && (prefix || str.size() == 6)) {
return HeapType::string;
}
if (str.substr(0, 15) == "stringview_wtf8" && (prefix || str.size() == 15)) {
return HeapType::stringview_wtf8;
}
if (str.substr(0, 16) == "stringview_wtf16" && (prefix || str.size() == 16)) {
return HeapType::stringview_wtf16;
}
if (str.substr(0, 15) == "stringview_iter" && (prefix || str.size() == 15)) {
return HeapType::stringview_iter;
}
if (str.substr(0, 4) == "none" && (prefix || str.size() == 4)) {
return HeapType::none;
}
if (str.substr(0, 8) == "noextern" && (prefix || str.size() == 8)) {
return HeapType::noext;
}
if (str.substr(0, 6) == "nofunc" && (prefix || str.size() == 6)) {
return HeapType::nofunc;
}
throw ParseException("invalid wasm heap type: "s +
std::string(str.data(), str.size()));
}
Type SExpressionWasmBuilder::elementToType(Element& s) {
if (s.isStr()) {
return stringToType(s.str());
}
auto& list = s.list();
auto size = list.size();
if (elementStartsWith(s, REF)) {
// It's a reference. It should be in the form
// (ref $name)
// or
// (ref null $name)
// and also $name can be the expanded structure of the type and not a name,
// so something like (ref (func (result i32))), etc.
if (size != 2 && size != 3) {
throw ParseException(
"invalid reference type size: "s + s.toString(), s.line, s.col);
}
if (size == 3 && *list[1] != NULL_) {
throw ParseException(
"invalid reference type qualifier: "s + s.toString(), s.line, s.col);
}
Nullability nullable = NonNullable;
size_t i = 1;
if (size == 3) {
nullable = Nullable;
i++;
}
return Type(parseHeapType(*s[i]), nullable);
}
// It's a tuple.
std::vector<Type> types;
for (size_t i = 0; i < s.size(); ++i) {
types.push_back(elementToType(*list[i]));
}
return Type(types);
}
Type SExpressionWasmBuilder::stringToLaneType(const char* str) {
if (strcmp(str, "i8x16") == 0) {
return Type::i32;
}
if (strcmp(str, "i16x8") == 0) {
return Type::i32;
}
if (strcmp(str, "i32x4") == 0) {
return Type::i32;
}
if (strcmp(str, "i64x2") == 0) {
return Type::i64;
}
if (strcmp(str, "f32x4") == 0) {
return Type::f32;
}
if (strcmp(str, "f64x2") == 0) {
return Type::f64;
}
return Type::none;
}
HeapType SExpressionWasmBuilder::getFunctionType(Name name, Element& s) {
auto iter = functionTypes.find(name);
if (iter == functionTypes.end()) {
throw ParseException(
"invalid call target: " + name.toString(), s.line, s.col);
}
return iter->second;
}
Function::DebugLocation
SExpressionWasmBuilder::getDebugLocation(const SourceLocation& loc) {
IString file = loc.filename;
auto& debugInfoFileNames = wasm.debugInfoFileNames;
auto iter = debugInfoFileIndices.find(file);
if (iter == debugInfoFileIndices.end()) {
Index index = debugInfoFileNames.size();
debugInfoFileNames.push_back(file.toString());
debugInfoFileIndices[file] = index;
}
uint32_t fileIndex = debugInfoFileIndices[file];
return {fileIndex, loc.line, loc.column};
}
Expression* SExpressionWasmBuilder::parseExpression(Element& s) {
Expression* result = makeExpression(s);
if (s.startLoc && currFunction) {
currFunction->debugLocations[result] = getDebugLocation(*s.startLoc);
}
return result;
}
Expression* SExpressionWasmBuilder::makeExpression(Element& s){
#define INSTRUCTION_PARSER
#include "gen-s-parser.inc"
}
Expression* SExpressionWasmBuilder::makeUnreachable() {
return allocator.alloc<Unreachable>();
}
Expression* SExpressionWasmBuilder::makeNop() { return allocator.alloc<Nop>(); }
Expression* SExpressionWasmBuilder::makeBinary(Element& s, BinaryOp op) {
auto ret = allocator.alloc<Binary>();
ret->op = op;
ret->left = parseExpression(s[1]);
ret->right = parseExpression(s[2]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeUnary(Element& s, UnaryOp op) {
auto ret = allocator.alloc<Unary>();
ret->op = op;
ret->value = parseExpression(s[1]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeSelect(Element& s) {
auto ret = allocator.alloc<Select>();
Index i = 1;
Type type = parseOptionalResultType(s, i);
ret->ifTrue = parseExpression(s[i++]);
ret->ifFalse = parseExpression(s[i++]);
ret->condition = parseExpression(s[i]);
if (type.isConcrete()) {
ret->finalize(type);
} else {
ret->finalize();
}
return ret;
}
Expression* SExpressionWasmBuilder::makeDrop(Element& s) {
auto ret = allocator.alloc<Drop>();
ret->value = parseExpression(s[1]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeMemorySize(Element& s) {
auto ret = allocator.alloc<MemorySize>();
Index i = 1;
Name memory;
if (s.size() > 1) {
memory = getMemoryName(*s[i++]);
} else {
memory = getMemoryNameAtIdx(0);
}
ret->memory = memory;
if (isMemory64(memory)) {
ret->make64();
}
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeMemoryGrow(Element& s) {
auto ret = allocator.alloc<MemoryGrow>();
Index i = 1;
Name memory;
if (s.size() > 2) {
memory = getMemoryName(*s[i++]);
} else {
memory = getMemoryNameAtIdx(0);
}
ret->memory = memory;
if (isMemory64(memory)) {
ret->make64();
}
ret->delta = parseExpression(s[i]);
ret->finalize();
return ret;
}
Index SExpressionWasmBuilder::getLocalIndex(Element& s) {
if (!currFunction) {
throw ParseException(
"local access in non-function scope: "s + s.toString(), s.line, s.col);
}
if (s.dollared()) {
auto ret = s.str();
if (currFunction->localIndices.count(ret) == 0) {
throw ParseException("bad local name: "s + s.toString(), s.line, s.col);
}
return currFunction->getLocalIndex(ret);
}
// this is a numeric index
Index ret = parseIndex(s);
if (ret >= currFunction->getNumLocals()) {
throw ParseException("bad local index: "s + s.toString(), s.line, s.col);
}
return ret;
}
Expression* SExpressionWasmBuilder::makeLocalGet(Element& s) {
auto ret = allocator.alloc<LocalGet>();
ret->index = getLocalIndex(*s[1]);
ret->type = currFunction->getLocalType(ret->index);
return ret;
}
Expression* SExpressionWasmBuilder::makeLocalTee(Element& s) {
auto ret = allocator.alloc<LocalSet>();
ret->index = getLocalIndex(*s[1]);
ret->value = parseExpression(s[2]);
ret->makeTee(currFunction->getLocalType(ret->index));
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeLocalSet(Element& s) {
auto ret = allocator.alloc<LocalSet>();
ret->index = getLocalIndex(*s[1]);
ret->value = parseExpression(s[2]);
ret->makeSet();
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeGlobalGet(Element& s) {
auto ret = allocator.alloc<GlobalGet>();
ret->name = getGlobalName(*s[1]);
auto* global = wasm.getGlobalOrNull(ret->name);
if (!global) {
throw ParseException(
"bad global.get name: "s + s.toString(), s.line, s.col);
}
ret->type = global->type;
return ret;
}
Expression* SExpressionWasmBuilder::makeGlobalSet(Element& s) {
auto ret = allocator.alloc<GlobalSet>();
ret->name = getGlobalName(*s[1]);
if (wasm.getGlobalOrNull(ret->name) &&
!wasm.getGlobalOrNull(ret->name)->mutable_) {
throw ParseException(
"global.set of immutable: "s + s.toString(), s.line, s.col);
}
ret->value = parseExpression(s[2]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeBlock(Element& s) {
if (!currFunction) {
throw ParseException("block is unallowed outside of functions: " +
s.toString(),
s.line,
s.col);
}
// special-case Block, because Block nesting (in their first element) can be
// incredibly deep
auto curr = allocator.alloc<Block>();
auto* sp = &s;
// The information we need for the stack of blocks here is the element we are
// converting, the block we are converting it to, and whether it originally
// had a name or not (which will be useful later).
struct Info {
Element* element;
Block* block;
bool hadName;
};
std::vector<Info> stack;
while (1) {
auto& s = *sp;
Index i = 1;
Name sName;
bool hadName = false;
if (i < s.size() && s[i]->isStr()) {
// could be a name or a type
if (s[i]->dollared() ||
stringToType(s[i]->str(), true /* allowError */) == Type::none) {
sName = s[i++]->str();
hadName = true;
} else {
sName = "block";
}
} else {
sName = "block";
}
stack.emplace_back(Info{sp, curr, hadName});
curr->name = nameMapper.pushLabelName(sName);
// block signature
curr->type = parseOptionalResultType(s, i);
if (i >= s.size()) {
break; // empty block
}
auto& first = *s[i];
if (elementStartsWith(first, BLOCK)) {
// recurse
curr = allocator.alloc<Block>();
if (first.startLoc) {
currFunction->debugLocations[curr] = getDebugLocation(*first.startLoc);
}
sp = &first;
continue;
}
break;
}
// we now have a stack of Blocks, with their labels, but no contents yet
for (int t = int(stack.size()) - 1; t >= 0; t--) {
auto* sp = stack[t].element;
auto* curr = stack[t].block;
auto hadName = stack[t].hadName;
auto& s = *sp;
size_t i = 1;
if (i < s.size()) {
while (i < s.size() && s[i]->isStr()) {
i++;
}
if (i < s.size() && elementStartsWith(*s[i], RESULT)) {
i++;
}
if (t < int(stack.size()) - 1) {
// first child is one of our recursions
curr->list.push_back(stack[t + 1].block);
i++;
}
for (; i < s.size(); i++) {
curr->list.push_back(parseExpression(s[i]));
}
}
nameMapper.popLabelName(curr->name);
curr->finalize(curr->type);
// If the block never had a name, and one was not needed in practice (even
// if one did not exist, perhaps a break targeted it by index), then we can
// remove the name. Note that we only do this if it never had a name: if it
// did, we don't want to change anything; we just want to be the same as
// the code we are loading - if there was no name before, we don't want one
// now, so that we roundtrip text precisely.
if (!hadName && !BranchUtils::BranchSeeker::has(curr, curr->name)) {
curr->name = Name();
}
}
return stack[0].block;
}
// Similar to block, but the label is handled by the enclosing if (since there
// might not be a then or else, ick)
Expression* SExpressionWasmBuilder::makeThenOrElse(Element& s) {
auto ret = allocator.alloc<Block>();
size_t i = 1;
if (s.size() > 1 && s[1]->isStr()) {
i++;
}
for (; i < s.size(); i++) {
ret->list.push_back(parseExpression(s[i]));
}
ret->finalize();
return ret;
}
static Expression* parseConst(IString s, Type type, MixedArena& allocator) {
const char* str = s.str.data();
auto ret = allocator.alloc<Const>();
ret->type = type;
if (type.isFloat()) {
if (s == _INFINITY) {
switch (type.getBasic()) {
case Type::f32:
ret->value = Literal(std::numeric_limits<float>::infinity());
break;
case Type::f64:
ret->value = Literal(std::numeric_limits<double>::infinity());
break;
default:
return nullptr;
}
return ret;
}
if (s == NEG_INFINITY) {
switch (type.getBasic()) {
case Type::f32:
ret->value = Literal(-std::numeric_limits<float>::infinity());
break;
case Type::f64:
ret->value = Literal(-std::numeric_limits<double>::infinity());
break;
default:
return nullptr;
}
return ret;
}
if (s == _NAN) {
switch (type.getBasic()) {
case Type::f32:
ret->value = Literal(float(std::nan("")));
break;
case Type::f64:
ret->value = Literal(double(std::nan("")));
break;
default:
return nullptr;
}
return ret;
}
bool negative = str[0] == '-';
const char* positive = negative ? str + 1 : str;
if (!negative) {
if (positive[0] == '+') {
positive++;
}
}
if (positive[0] == 'n' && positive[1] == 'a' && positive[2] == 'n') {
const char* modifier = positive[3] == ':' ? positive + 4 : nullptr;
if (!(modifier ? positive[4] == '0' && positive[5] == 'x' : 1)) {
throw ParseException("bad nan input: "s + str);
}
switch (type.getBasic()) {
case Type::f32: {
uint32_t pattern;
if (modifier) {
std::istringstream istr(modifier);
istr >> std::hex >> pattern;
if (istr.fail()) {
throw ParseException("invalid f32 format: "s + str);
}
pattern |= 0x7f800000U;
} else {
pattern = 0x7fc00000U;
}
if (negative) {
pattern |= 0x80000000U;
}
if (!std::isnan(bit_cast<float>(pattern))) {
pattern |= 1U;
}
ret->value = Literal(pattern).castToF32();
break;
}
case Type::f64: {
uint64_t pattern;
if (modifier) {
std::istringstream istr(modifier);
istr >> std::hex >> pattern;
if (istr.fail()) {
throw ParseException("invalid f64 format: "s + str);
}
pattern |= 0x7ff0000000000000ULL;
} else {
pattern = 0x7ff8000000000000UL;
}
if (negative) {
pattern |= 0x8000000000000000ULL;
}
if (!std::isnan(bit_cast<double>(pattern))) {
pattern |= 1ULL;
}
ret->value = Literal(pattern).castToF64();
break;
}
default:
return nullptr;
}
// std::cerr << "make constant " << str << " ==> " << ret->value << '\n';
return ret;
}
if (s == NEG_NAN) {
switch (type.getBasic()) {
case Type::f32:
ret->value = Literal(float(-std::nan("")));
break;
case Type::f64:
ret->value = Literal(double(-std::nan("")));
break;
default:
return nullptr;
}
// std::cerr << "make constant " << str << " ==> " << ret->value << '\n';
return ret;
}
}
switch (type.getBasic()) {
case Type::i32: {
if ((str[0] == '0' && str[1] == 'x') ||
(str[0] == '-' && str[1] == '0' && str[2] == 'x')) {
bool negative = str[0] == '-';
if (negative) {
str++;
}
std::istringstream istr(str);
uint32_t temp;
istr >> std::hex >> temp;
if (istr.fail()) {
throw ParseException("invalid i32 format: "s + str);
}
ret->value = Literal(negative ? -temp : temp);
} else {
std::istringstream istr(str[0] == '-' ? str + 1 : str);
uint32_t temp;
istr >> temp;
if (istr.fail()) {
throw ParseException("invalid i32 format: "s + str);
}
ret->value = Literal(str[0] == '-' ? -temp : temp);
}
break;
}
case Type::i64: {
if ((str[0] == '0' && str[1] == 'x') ||
(str[0] == '-' && str[1] == '0' && str[2] == 'x')) {
bool negative = str[0] == '-';
if (negative) {
str++;
}
std::istringstream istr(str);
uint64_t temp;
istr >> std::hex >> temp;
if (istr.fail()) {
throw ParseException("invalid i64 format: "s + str);
}
ret->value = Literal(negative ? -temp : temp);
} else {
std::istringstream istr(str[0] == '-' ? str + 1 : str);
uint64_t temp;
istr >> temp;
if (istr.fail()) {
throw ParseException("invalid i64 format: "s + str);
}
ret->value = Literal(str[0] == '-' ? -temp : temp);
}
break;
}
case Type::f32: {
char* end;
ret->value = Literal(strtof(str, &end));
break;
}
case Type::f64: {
char* end;
ret->value = Literal(strtod(str, &end));
break;
}
case Type::v128:
WASM_UNREACHABLE("unexpected const type");
case Type::none:
case Type::unreachable: {
return nullptr;
}
}
if (ret->value.type != type) {
throw ParseException("parsed type does not match expected type: "s + str);
}
return ret;
}
template<int Lanes>
static Literal makeLanes(Element& s, MixedArena& allocator, Type lane_t) {
std::array<Literal, Lanes> lanes;
for (size_t i = 0; i < Lanes; ++i) {
Expression* lane = parseConst(s[i + 2]->str(), lane_t, allocator);
if (lane) {
lanes[i] = lane->cast<Const>()->value;
} else {
throw ParseException("Could not parse v128 lane: "s + s.toString(),
s[i + 2]->line,
s[i + 2]->col);
}
}
return Literal(lanes);
}
Expression* SExpressionWasmBuilder::makeConst(Element& s, Type type) {
if (type != Type::v128) {
auto ret = parseConst(s[1]->str(), type, allocator);
if (!ret) {
throw ParseException(
"bad const: "s + s.toString(), s[1]->line, s[1]->col);
}
return ret;
}
auto ret = allocator.alloc<Const>();
Type lane_t = stringToLaneType(s[1]->str().str.data());
size_t lanes = s.size() - 2;
switch (lanes) {
case 2: {
if (lane_t != Type::i64 && lane_t != Type::f64) {
throw ParseException("Unexpected v128 literal lane type: "s +
s.toString(),
s[1]->line,
s[1]->col);
}
ret->value = makeLanes<2>(s, allocator, lane_t);
break;
}
case 4: {
if (lane_t != Type::i32 && lane_t != Type::f32) {
throw ParseException("Unexpected v128 literal lane type: "s +
s.toString(),
s[1]->line,
s[1]->col);
}
ret->value = makeLanes<4>(s, allocator, lane_t);
break;
}
case 8: {
if (lane_t != Type::i32) {
throw ParseException("Unexpected v128 literal lane type: "s +
s.toString(),
s[1]->line,
s[1]->col);
}
ret->value = makeLanes<8>(s, allocator, lane_t);
break;
}
case 16: {
if (lane_t != Type::i32) {
throw ParseException("Unexpected v128 literal lane type: "s +
s.toString(),
s[1]->line,
s[1]->col);
}
ret->value = makeLanes<16>(s, allocator, lane_t);
break;
}
default:
throw ParseException("Unexpected number of lanes in v128 literal: "s +
s.toString(),
s[1]->line,
s[1]->col);
}
ret->finalize();
return ret;
}
static size_t parseMemAttributes(size_t i,
Element& s,
Address& offset,
Address& align,
bool memory64) {
// Parse "align=X" and "offset=X" arguments, bailing out on anything else.
while (!s[i]->isList()) {
const char* str = s[i]->str().str.data();
if (strncmp(str, "align", 5) != 0 && strncmp(str, "offset", 6) != 0) {
return i;
}
const char* eq = strchr(str, '=');
if (!eq) {
throw ParseException("missing = in memory attribute: "s + s.toString(),
s[i]->line,
s[i]->col);
}
eq++;
if (*eq == 0) {
throw ParseException("missing value in memory attribute: "s +
s.toString(),
s[i]->line,
s[i]->col);
}
char* endptr;
uint64_t value = strtoll(eq, &endptr, 10);
if (*endptr != 0) {
throw ParseException("bad memory attribute immediate: "s + s.toString(),
s[i]->line,
s[i]->col);
}
if (str[0] == 'a') {
if (value > std::numeric_limits<uint32_t>::max()) {
throw ParseException(
"bad align: "s + s.toString(), s[i]->line, s[i]->col);
}
align = value;
} else if (str[0] == 'o') {
if (!memory64 && value > std::numeric_limits<uint32_t>::max()) {
throw ParseException(
"bad offset: "s + s.toString(), s[i]->line, s[i]->col);
}
offset = value;
} else {
throw ParseException(
"bad memory attribute: "s + s.toString(), s[i]->line, s[i]->col);
}
i++;
}
return i;
}
bool SExpressionWasmBuilder::hasMemoryIdx(Element& s,
Index defaultSize,
Index i) {
if (s.size() > defaultSize && !s[i]->isList() &&
strncmp(s[i]->str().str.data(), "align", 5) != 0 &&
strncmp(s[i]->str().str.data(), "offset", 6) != 0) {
return true;
}
return false;
}
Expression* SExpressionWasmBuilder::makeLoad(
Element& s, Type type, bool signed_, int bytes, bool isAtomic) {
auto* ret = allocator.alloc<Load>();
ret->type = type;
ret->bytes = bytes;
ret->signed_ = signed_;
ret->offset = 0;
ret->align = bytes;
ret->isAtomic = isAtomic;
Index i = 1;
Name memory;
// Check to make sure there are more than the default args & this str isn't
// the mem attributes
if (hasMemoryIdx(s, 2, i)) {
memory = getMemoryName(*s[i++]);
} else {
memory = getMemoryNameAtIdx(0);
}
ret->memory = memory;
i = parseMemAttributes(i, s, ret->offset, ret->align, isMemory64(memory));
ret->ptr = parseExpression(s[i]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeStore(Element& s,
Type type,
int bytes,
bool isAtomic) {
auto ret = allocator.alloc<Store>();
ret->bytes = bytes;
ret->offset = 0;
ret->align = bytes;
ret->isAtomic = isAtomic;
ret->valueType = type;
Index i = 1;
Name memory;
// Check to make sure there are more than the default args & this str isn't
// the mem attributes
if (hasMemoryIdx(s, 3, i)) {
memory = getMemoryName(*s[i++]);
} else {
memory = getMemoryNameAtIdx(0);
}
ret->memory = memory;
i = parseMemAttributes(i, s, ret->offset, ret->align, isMemory64(memory));
ret->ptr = parseExpression(s[i]);
ret->value = parseExpression(s[i + 1]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeAtomicRMW(Element& s,
AtomicRMWOp op,
Type type,
uint8_t bytes) {
auto ret = allocator.alloc<AtomicRMW>();
ret->type = type;
ret->op = op;
ret->bytes = bytes;
ret->offset = 0;
Index i = 1;
Name memory;
// Check to make sure there are more than the default args & this str isn't
// the mem attributes
if (hasMemoryIdx(s, 3, i)) {
memory = getMemoryName(*s[i++]);
} else {
memory = getMemoryNameAtIdx(0);
}
ret->memory = memory;
Address align = bytes;
i = parseMemAttributes(i, s, ret->offset, align, isMemory64(memory));
if (align != ret->bytes) {
throw ParseException(
"Align of Atomic RMW must match size: "s + s.toString(), s.line, s.col);
}
ret->ptr = parseExpression(s[i]);
ret->value = parseExpression(s[i + 1]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeAtomicCmpxchg(Element& s,
Type type,
uint8_t bytes) {
auto ret = allocator.alloc<AtomicCmpxchg>();
ret->type = type;
ret->bytes = bytes;
ret->offset = 0;
Index i = 1;
Name memory;
// Check to make sure there are more than the default args & this str isn't
// the mem attributes
if (hasMemoryIdx(s, 4, i)) {
memory = getMemoryName(*s[i++]);
} else {
memory = getMemoryNameAtIdx(0);
}
ret->memory = memory;
Address align = ret->bytes;
i = parseMemAttributes(i, s, ret->offset, align, isMemory64(memory));
if (align != ret->bytes) {
throw ParseException("Align of Atomic Cmpxchg must match size: "s +
s.toString(),
s.line,
s.col);
}
ret->ptr = parseExpression(s[i]);
ret->expected = parseExpression(s[i + 1]);
ret->replacement = parseExpression(s[i + 2]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeAtomicWait(Element& s, Type type) {
auto ret = allocator.alloc<AtomicWait>();
ret->type = Type::i32;
ret->offset = 0;
ret->expectedType = type;
Index i = 1;
Name memory;
// Check to make sure there are more than the default args & this str isn't
// the mem attributes
if (hasMemoryIdx(s, 4, i)) {
memory = getMemoryName(*s[i++]);
} else {
memory = getMemoryNameAtIdx(0);
}
ret->memory = memory;
Address expectedAlign = type == Type::i64 ? 8 : 4;
Address align = expectedAlign;
i = parseMemAttributes(i, s, ret->offset, align, isMemory64(memory));
if (align != expectedAlign) {
throw ParseException("Align of memory.atomic.wait must match size: "s +
s.toString(),
s.line,
s.col);
}
ret->ptr = parseExpression(s[i]);
ret->expected = parseExpression(s[i + 1]);
ret->timeout = parseExpression(s[i + 2]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeAtomicNotify(Element& s) {
auto ret = allocator.alloc<AtomicNotify>();
ret->type = Type::i32;
ret->offset = 0;
Index i = 1;
Name memory;
// Check to make sure there are more than the default args & this str isn't
// the mem attributes
if (hasMemoryIdx(s, 3, i)) {
memory = getMemoryName(*s[i++]);
} else {
memory = getMemoryNameAtIdx(0);
}
ret->memory = memory;
Address align = 4;
i = parseMemAttributes(i, s, ret->offset, align, isMemory64(memory));
if (align != 4) {
throw ParseException("Align of memory.atomic.notify must be 4: "s +
s.toString(),
s.line,
s.col);
}
ret->ptr = parseExpression(s[i]);
ret->notifyCount = parseExpression(s[i + 1]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeAtomicFence(Element& s) {
return allocator.alloc<AtomicFence>();
}
static uint8_t parseLaneIndex(const Element* s, size_t lanes) {
const char* str = s->str().str.data();
char* end;
auto n = static_cast<unsigned long long>(strtoll(str, &end, 10));
if (end == str || *end != '\0') {
throw ParseException(
"Expected lane index: "s + s->toString(), s->line, s->col);
}
if (n > lanes) {
throw ParseException("lane index must be less than " +
std::to_string(lanes) + ": " + s->toString(),
s->line,
s->col);
}
return uint8_t(n);
}
Expression* SExpressionWasmBuilder::makeSIMDExtract(Element& s,
SIMDExtractOp op,
size_t lanes) {
auto ret = allocator.alloc<SIMDExtract>();
ret->op = op;
ret->index = parseLaneIndex(s[1], lanes);
ret->vec = parseExpression(s[2]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeSIMDReplace(Element& s,
SIMDReplaceOp op,
size_t lanes) {
auto ret = allocator.alloc<SIMDReplace>();
ret->op = op;
ret->index = parseLaneIndex(s[1], lanes);
ret->vec = parseExpression(s[2]);
ret->value = parseExpression(s[3]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeSIMDShuffle(Element& s) {
auto ret = allocator.alloc<SIMDShuffle>();
for (size_t i = 0; i < 16; ++i) {
ret->mask[i] = parseLaneIndex(s[i + 1], 32);
}
ret->left = parseExpression(s[17]);
ret->right = parseExpression(s[18]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeSIMDTernary(Element& s,
SIMDTernaryOp op) {
auto ret = allocator.alloc<SIMDTernary>();
ret->op = op;
ret->a = parseExpression(s[1]);
ret->b = parseExpression(s[2]);
ret->c = parseExpression(s[3]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeSIMDShift(Element& s, SIMDShiftOp op) {
auto ret = allocator.alloc<SIMDShift>();
ret->op = op;
ret->vec = parseExpression(s[1]);
ret->shift = parseExpression(s[2]);
ret->finalize();
return ret;
}
Expression*
SExpressionWasmBuilder::makeSIMDLoad(Element& s, SIMDLoadOp op, int bytes) {
auto ret = allocator.alloc<SIMDLoad>();
ret->op = op;
ret->offset = 0;
ret->align = bytes;
Index i = 1;
Name memory;
// Check to make sure there are more than the default args & this str isn't
// the mem attributes
if (hasMemoryIdx(s, 2, i)) {
memory = getMemoryName(*s[i++]);
} else {
memory = getMemoryNameAtIdx(0);
}
ret->memory = memory;
i = parseMemAttributes(i, s, ret->offset, ret->align, isMemory64(memory));
ret->ptr = parseExpression(s[i]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeSIMDLoadStoreLane(
Element& s, SIMDLoadStoreLaneOp op, int bytes) {
auto* ret = allocator.alloc<SIMDLoadStoreLane>();
ret->op = op;
ret->offset = 0;
ret->align = bytes;
size_t lanes;
switch (op) {
case Load8LaneVec128:
case Store8LaneVec128:
lanes = 16;
break;
case Load16LaneVec128:
case Store16LaneVec128:
lanes = 8;
break;
case Load32LaneVec128:
case Store32LaneVec128:
lanes = 4;
break;
case Load64LaneVec128:
case Store64LaneVec128:
lanes = 2;
break;
default:
WASM_UNREACHABLE("Unexpected SIMDLoadStoreLane op");
}
Index i = 1;
Name memory;
// Check to make sure there are more than the default args & this str isn't
// the mem attributes
if (hasMemoryIdx(s, 4, i)) {
memory = getMemoryName(*s[i++]);
} else {
memory = getMemoryNameAtIdx(0);
}
ret->memory = memory;
i = parseMemAttributes(i, s, ret->offset, ret->align, isMemory64(memory));
ret->index = parseLaneIndex(s[i++], lanes);
ret->ptr = parseExpression(s[i++]);
ret->vec = parseExpression(s[i]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeMemoryInit(Element& s) {
auto ret = allocator.alloc<MemoryInit>();
Index i = 1;
Name memory;
if (s.size() > 5) {
memory = getMemoryName(*s[i++]);
} else {
memory = getMemoryNameAtIdx(0);
}
ret->memory = memory;
ret->segment = getDataSegmentName(*s[i++]);
ret->dest = parseExpression(s[i++]);
ret->offset = parseExpression(s[i++]);
ret->size = parseExpression(s[i]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeDataDrop(Element& s) {
auto ret = allocator.alloc<DataDrop>();
ret->segment = getDataSegmentName(*s[1]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeMemoryCopy(Element& s) {
auto ret = allocator.alloc<MemoryCopy>();
Index i = 1;
Name destMemory;
Name sourceMemory;
if (s.size() > 4) {
destMemory = getMemoryName(*s[i++]);
sourceMemory = getMemoryName(*s[i++]);
} else {
destMemory = getMemoryNameAtIdx(0);
sourceMemory = getMemoryNameAtIdx(0);
}
ret->destMemory = destMemory;
ret->sourceMemory = sourceMemory;
ret->dest = parseExpression(s[i++]);
ret->source = parseExpression(s[i++]);
ret->size = parseExpression(s[i]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeMemoryFill(Element& s) {
auto ret = allocator.alloc<MemoryFill>();
Index i = 1;
Name memory;
if (s.size() > 4) {
memory = getMemoryName(*s[i++]);
} else {
memory = getMemoryNameAtIdx(0);
}
ret->memory = memory;
ret->dest = parseExpression(s[i++]);
ret->value = parseExpression(s[i++]);
ret->size = parseExpression(s[i]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makePop(Element& s) {
auto ret = allocator.alloc<Pop>();
std::vector<Type> types;
for (size_t i = 1; i < s.size(); ++i) {
types.push_back(elementToType(*s[i]));
}
ret->type = Type(types);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeIf(Element& s) {
auto ret = allocator.alloc<If>();
Index i = 1;
Name sName;
if (s[i]->dollared()) {
// the if is labeled
sName = s[i++]->str();
} else {
sName = "if";
}
auto label = nameMapper.pushLabelName(sName);
// if signature
Type type = parseOptionalResultType(s, i);
ret->condition = parseExpression(s[i++]);
ret->ifTrue = parseExpression(*s[i++]);
if (i < s.size()) {
ret->ifFalse = parseExpression(*s[i++]);
}
ret->finalize(type);
nameMapper.popLabelName(label);
// create a break target if we must
if (BranchUtils::BranchSeeker::has(ret, label)) {
auto* block = allocator.alloc<Block>();
block->name = label;
block->list.push_back(ret);
block->finalize(type);
return block;
}
return ret;
}
Expression*
SExpressionWasmBuilder::makeMaybeBlock(Element& s, size_t i, Type type) {
Index stopAt = -1;
if (s.size() == i) {
return allocator.alloc<Nop>();
}
if (s.size() == i + 1) {
return parseExpression(s[i]);
}
auto ret = allocator.alloc<Block>();
for (; i < s.size() && i < stopAt; i++) {
ret->list.push_back(parseExpression(s[i]));
}
ret->finalize(type);
// Note that we do not name these implicit/synthetic blocks. They
// are the effects of syntactic sugar, and nothing can branch to
// them anyhow.
return ret;
}
Type SExpressionWasmBuilder::parseOptionalResultType(Element& s, Index& i) {
if (s.size() == i) {
return Type::none;
}
// TODO(sbc): Remove support for old result syntax (bare streing) once the
// spec tests are updated.
if (s[i]->isStr()) {
return stringToType(s[i++]->str());
}
Element& results = *s[i];
IString id = results[0]->str();
if (id == RESULT) {
i++;
return Type(parseResults(results));
}
return Type::none;
}
Expression* SExpressionWasmBuilder::makeLoop(Element& s) {
auto ret = allocator.alloc<Loop>();
Index i = 1;
Name sName;
if (s.size() > i && s[i]->dollared()) {
sName = s[i++]->str();
} else {
sName = "loop-in";
}
ret->name = nameMapper.pushLabelName(sName);
ret->type = parseOptionalResultType(s, i);
ret->body = makeMaybeBlock(s, i, ret->type);
nameMapper.popLabelName(ret->name);
ret->finalize(ret->type);
return ret;
}
Expression* SExpressionWasmBuilder::makeCall(Element& s, bool isReturn) {
auto target = getFunctionName(*s[1]);
auto ret = allocator.alloc<Call>();
ret->target = target;
ret->type = getFunctionType(ret->target, s).getSignature().results;
parseCallOperands(s, 2, s.size(), ret);
ret->isReturn = isReturn;
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeCallIndirect(Element& s,
bool isReturn) {
if (wasm.tables.empty()) {
throw ParseException("no tables: " + s.toString(), s.line, s.col);
}
Index i = 1;
auto ret = allocator.alloc<CallIndirect>();
if (s[i]->isStr()) {
ret->table = s[i++]->str();
} else {
ret->table = wasm.tables.front()->name;
}
HeapType callType;
i = parseTypeUse(s, i, callType);
ret->heapType = callType;
parseCallOperands(s, i, s.size() - 1, ret);
ret->target = parseExpression(s[s.size() - 1]);
ret->isReturn = isReturn;
ret->finalize();
return ret;
}
Name SExpressionWasmBuilder::getLabel(Element& s, LabelType labelType) {
if (s.dollared()) {
return nameMapper.sourceToUnique(s.str());
} else {
// offset, break to nth outside label
uint64_t offset;
try {
offset = std::stoll(s.toString(), nullptr, 0);
} catch (std::invalid_argument&) {
throw ParseException(
"invalid break offset: "s + s.toString(), s.line, s.col);
} catch (std::out_of_range&) {
throw ParseException(
"out of range break offset: "s + s.toString(), s.line, s.col);
}
if (offset > nameMapper.labelStack.size()) {
throw ParseException("invalid label: "s + s.toString(), s.line, s.col);
}
if (offset == nameMapper.labelStack.size()) {
if (labelType == LabelType::Break) {
// a break to the function's scope. this means we need an automatic
// block, with a name
brokeToAutoBlock = true;
return FAKE_RETURN;
}
// This is a delegate that delegates to the caller
return DELEGATE_CALLER_TARGET;
}
return nameMapper.labelStack[nameMapper.labelStack.size() - 1 - offset];
}
}
Expression* SExpressionWasmBuilder::makeBreak(Element& s) {
auto ret = allocator.alloc<Break>();
size_t i = 1;
ret->name = getLabel(*s[i]);
i++;
if (i == s.size()) {
return ret;
}
if (elementStartsWith(s, BR_IF)) {
if (i + 1 < s.size()) {
ret->value = parseExpression(s[i]);
i++;
}
ret->condition = parseExpression(s[i]);
} else {
ret->value = parseExpression(s[i]);
}
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeBreakTable(Element& s) {
auto ret = allocator.alloc<Switch>();
size_t i = 1;
while (!s[i]->isList()) {
ret->targets.push_back(getLabel(*s[i++]));
}
if (ret->targets.size() == 0) {
throw ParseException(
"switch with no targets: "s + s.toString(), s.line, s.col);
}
ret->default_ = ret->targets.back();
ret->targets.pop_back();
ret->condition = parseExpression(s[i++]);
if (i < s.size()) {
ret->value = ret->condition;
ret->condition = parseExpression(s[i++]);
}
return ret;
}
Expression* SExpressionWasmBuilder::makeReturn(Element& s) {
auto ret = allocator.alloc<Return>();
if (s.size() >= 2) {
ret->value = parseExpression(s[1]);
}
return ret;
}
Expression* SExpressionWasmBuilder::makeRefNull(Element& s) {
if (s.size() != 2) {
throw ParseException(
"invalid heap type reference: "s + s.toString(), s.line, s.col);
}
auto ret = allocator.alloc<RefNull>();
// The heap type may be just "func", that is, the whole thing is just
// (ref.null func), or it may be the name of a defined type, such as
// (ref.null $struct.FOO)
if (s[1]->dollared()) {
ret->finalize(parseHeapType(*s[1]).getBottom());
} else {
ret->finalize(stringToHeapType(s[1]->str()).getBottom());
}
return ret;
}
Expression* SExpressionWasmBuilder::makeRefIsNull(Element& s) {
auto ret = allocator.alloc<RefIsNull>();
ret->value = parseExpression(s[1]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeRefFunc(Element& s) {
auto func = getFunctionName(*s[1]);
auto ret = allocator.alloc<RefFunc>();
ret->func = func;
// To support typed function refs, we give the reference not just a general
// funcref, but a specific subtype with the actual signature.
ret->finalize(Type(getFunctionType(func, s), NonNullable));
return ret;
}
Expression* SExpressionWasmBuilder::makeRefEq(Element& s) {
auto ret = allocator.alloc<RefEq>();
ret->left = parseExpression(s[1]);
ret->right = parseExpression(s[2]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeTableGet(Element& s) {
auto tableName = s[1]->str();
auto* index = parseExpression(s[2]);
auto* table = wasm.getTableOrNull(tableName);
if (!table) {
throw ParseException(
"invalid table name in table.get: "s + s.toString(), s.line, s.col);
}
return Builder(wasm).makeTableGet(tableName, index, table->type);
}
Expression* SExpressionWasmBuilder::makeTableSet(Element& s) {
auto tableName = s[1]->str();
auto* table = wasm.getTableOrNull(tableName);
if (!table) {
throw ParseException(
"invalid table name in table.set: "s + s.toString(), s.line, s.col);
}
auto* index = parseExpression(s[2]);
auto* value = parseExpression(s[3]);
return Builder(wasm).makeTableSet(tableName, index, value);
}
Expression* SExpressionWasmBuilder::makeTableSize(Element& s) {
auto tableName = s[1]->str();
auto* table = wasm.getTableOrNull(tableName);
if (!table) {
throw ParseException(
"invalid table name in table.size: "s + s.toString(), s.line, s.col);
}
return Builder(wasm).makeTableSize(tableName);
}
Expression* SExpressionWasmBuilder::makeTableGrow(Element& s) {
auto tableName = s[1]->str();
auto* table = wasm.getTableOrNull(tableName);
if (!table) {
throw ParseException(
"invalid table name in table.grow: "s + s.toString(), s.line, s.col);
}
auto* value = parseExpression(s[2]);
if (!value->type.isRef()) {
throw ParseException("only reference types are valid for tables: "s +
s.toString(),
s.line,
s.col);
}
auto* delta = parseExpression(s[3]);
return Builder(wasm).makeTableGrow(tableName, value, delta);
}
Expression* SExpressionWasmBuilder::makeTableFill(Element& s) {
auto tableName = s[1]->str();
auto* table = wasm.getTableOrNull(tableName);
if (!table) {
throw ParseException(
"invalid table name in table.fill: "s + s.toString(), s.line, s.col);
}
auto* dest = parseExpression(s[2]);
auto* value = parseExpression(s[3]);
auto* size = parseExpression(s[4]);
return Builder(wasm).makeTableFill(tableName, dest, value, size);
}
// try can be either in the form of try-catch or try-delegate.
// try-catch is written in the folded wast format as
// (try
// (do
// ...
// )
// (catch $e
// ...
// )
// ...
// (catch_all
// ...
// )
// )
// Any number of catch blocks can exist, including none. Zero or one catch_all
// block can exist, and if it does, it should be at the end. There should be at
// least one catch or catch_all body per try.
//
// try-delegate is written in the folded format as
// (try
// (do
// ...
// )
// (delegate $label)
// )
Expression* SExpressionWasmBuilder::makeTry(Element& s) {
auto ret = allocator.alloc<Try>();
Index i = 1;
Name sName;
if (s[i]->dollared()) {
// the try is labeled
sName = s[i++]->str();
} else {
sName = "try";
}
ret->name = nameMapper.pushLabelName(sName);
Type type = parseOptionalResultType(s, i); // signature
if (!elementStartsWith(*s[i], "do")) {
throw ParseException("try body should start with 'do': "s + s.toString(),
s[i]->line,
s[i]->col);
}
ret->body = makeMaybeBlock(*s[i++], 1, type);
while (i < s.size() && elementStartsWith(*s[i], "catch")) {
Element& inner = *s[i++];
if (inner.size() < 2) {
throw ParseException(
"invalid catch block: "s + inner.toString(), inner.line, inner.col);
}
Name tag = getTagName(*inner[1]);
if (!wasm.getTagOrNull(tag)) {
throw ParseException(
"bad tag name: "s + inner.toString(), inner[1]->line, inner[1]->col);
}
ret->catchTags.push_back(getTagName(*inner[1]));
ret->catchBodies.push_back(makeMaybeBlock(inner, 2, type));
}
if (i < s.size() && elementStartsWith(*s[i], "catch_all")) {
ret->catchBodies.push_back(makeMaybeBlock(*s[i++], 1, type));
}
// 'delegate' cannot target its own try. So we pop the name here.
nameMapper.popLabelName(ret->name);
if (i < s.size() && elementStartsWith(*s[i], "delegate")) {
Element& inner = *s[i++];
if (inner.size() != 2) {
throw ParseException(
"invalid delegate: "s + inner.toString(), inner.line, inner.col);
}
ret->delegateTarget = getLabel(*inner[1], LabelType::Exception);
}
if (i != s.size()) {
throw ParseException(
"there should be at most one catch_all block at the end: "s +
s.toString(),
s.line,
s.col);
}
ret->finalize(type);
// create a break target if we must
if (BranchUtils::BranchSeeker::has(ret, ret->name)) {
auto* block = allocator.alloc<Block>();
// We create a different name for the wrapping block, because try's name can
// be used by internal delegates
block->name = nameMapper.pushLabelName(sName);
// For simplicity, try's name can only be targeted by delegates and
// rethrows. Make the branches target the new wrapping block instead.
BranchUtils::replaceBranchTargets(ret, ret->name, block->name);
block->list.push_back(ret);
nameMapper.popLabelName(block->name);
block->finalize(type);
return block;
}
return ret;
}
Expression* SExpressionWasmBuilder::makeThrow(Element& s) {
auto ret = allocator.alloc<Throw>();
Index i = 1;
ret->tag = getTagName(*s[i++]);
if (!wasm.getTagOrNull(ret->tag)) {
throw ParseException(
"bad tag name: "s + s.toString(), s[1]->line, s[1]->col);
}
for (; i < s.size(); i++) {
ret->operands.push_back(parseExpression(s[i]));
}
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeRethrow(Element& s) {
auto ret = allocator.alloc<Rethrow>();
ret->target = getLabel(*s[1], LabelType::Exception);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeTupleMake(Element& s) {
auto ret = allocator.alloc<TupleMake>();
parseCallOperands(s, 1, s.size(), ret);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeTupleExtract(Element& s) {
auto ret = allocator.alloc<TupleExtract>();
ret->index = parseIndex(*s[1]);
ret->tuple = parseExpression(s[2]);
if (ret->tuple->type != Type::unreachable &&
ret->index >= ret->tuple->type.size()) {
throw ParseException(
"Bad index on tuple.extract: "s + s.toString(), s[1]->line, s[1]->col);
}
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeCallRef(Element& s, bool isReturn) {
HeapType sigType = parseHeapType(*s[1]);
std::vector<Expression*> operands;
parseOperands(s, 2, s.size() - 1, operands);
auto* target = parseExpression(s[s.size() - 1]);
if (!sigType.isSignature()) {
throw ParseException(
std::string(isReturn ? "return_call_ref"s : "call_ref"s) +
" type annotation should be a signature: "s + s.toString(),
s.line,
s.col);
}
if (!Type::isSubType(target->type, Type(sigType, Nullable))) {
throw ParseException(
std::string(isReturn ? "return_call_ref"s : "call_ref"s) +
" target should match expected type: "s + s.toString(),
s.line,
s.col);
}
return Builder(wasm).makeCallRef(
target, operands, sigType.getSignature().results, isReturn);
}
Expression* SExpressionWasmBuilder::makeRefI31(Element& s) {
auto ret = allocator.alloc<RefI31>();
ret->value = parseExpression(s[1]);
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeI31Get(Element& s, bool signed_) {
auto ret = allocator.alloc<I31Get>();
ret->i31 = parseExpression(s[1]);
ret->signed_ = signed_;
ret->finalize();
return ret;
}
Expression* SExpressionWasmBuilder::makeRefTest(Element& s) {
Type castType = elementToType(*s[1]);
auto* ref = parseExpression(*s[2]);
return Builder(wasm).makeRefTest(ref, castType);
}
Expression* SExpressionWasmBuilder::makeRefCast(Element& s) {
Type castType = elementToType(*s[1]);
auto* ref = parseExpression(*s[2]);
return Builder(wasm).makeRefCast(ref, castType);
}
Expression* SExpressionWasmBuilder::makeBrOnNull(Element& s, bool onFail) {
int i = 1;
auto name = getLabel(*s[i++]);
auto* ref = parseExpression(*s[i]);
auto op = onFail ? BrOnNonNull : BrOnNull;
return Builder(wasm).makeBrOn(op, name, ref);
}
Expression* SExpressionWasmBuilder::makeBrOnCast(Element& s, bool onFail) {
int i = 1;
auto name = getLabel(*s[i++]);
auto inputType = elementToType(*s[i++]);
auto castType = elementToType(*s[i++]);
if (!Type::isSubType(castType, inputType)) {
throw ParseException(
"br_on_cast* cast type must be a subtype of its input type: "s +
s.toString(),
s.line,
s.col);
}
auto* ref = parseExpression(*s[i]);
if (!Type::isSubType(ref->type, inputType)) {
throw ParseException(
"br_on_cast* ref type does not match expected type: "s + s.toString(),
s.line,
s.col);
}
auto op = onFail ? BrOnCastFail : BrOnCast;
return Builder(wasm).makeBrOn(op, name, ref, castType);
}
Expression* SExpressionWasmBuilder::makeStructNew(Element& s, bool default_) {
auto heapType = parseHeapType(*s[1]);
auto numOperands = s.size() - 2;
if (default_ && numOperands > 0) {
throw ParseException(
"arguments provided for struct.new: "s + s.toString(), s.line, s.col);
}
std::vector<Expression*> operands;
operands.resize(numOperands);
for (Index i = 0; i < numOperands; i++) {
operands[i] = parseExpression(*s[i + 2]);
}
return Builder(wasm).makeStructNew(heapType, operands);
}
Index SExpressionWasmBuilder::getStructIndex(Element& type, Element& field) {
if (field.dollared()) {
auto name = field.str();
auto index = typeIndices[type.str().toString()];
auto struct_ = types[index].getStruct();
auto& fields = struct_.fields;
const auto& names = fieldNames[index];
for (Index i = 0; i < fields.size(); i++) {
auto it = names.find(i);
if (it != names.end() && it->second == name) {
return i;
}
}
throw ParseException(
"bad struct field name: "s + type.toString(), field.line, field.col);
}
// this is a numeric index
return parseIndex(field);
}
Expression* SExpressionWasmBuilder::makeStructGet(Element& s, bool signed_) {
auto heapType = parseHeapType(*s[1]);
if (!heapType.isStruct()) {
throw ParseException(
"bad struct heap type: "s + s.toString(), s.line, s.col);
}
auto index = getStructIndex(*s[1], *s[2]);
auto type = heapType.getStruct().fields[index].type;
auto ref = parseExpression(*s[3]);
validateHeapTypeUsingChild(ref, heapType, s);
return Builder(wasm).makeStructGet(index, ref, type, signed_);
}
Expression* SExpressionWasmBuilder::makeStructSet(Element& s) {
auto heapType = parseHeapType(*s[1]);
if (!heapType.isStruct()) {
throw ParseException(
"bad struct heap type: "s + s.toString(), s.line, s.col);
}
auto index = getStructIndex(*s[1], *s[2]);
auto ref = parseExpression(*s[3]);
validateHeapTypeUsingChild(ref, heapType, s);
auto value = parseExpression(*s[4]);
return Builder(wasm).makeStructSet(index, ref, value);
}
Expression* SExpressionWasmBuilder::makeArrayNew(Element& s, bool default_) {
auto heapType = parseHeapType(*s[1]);
Expression* init = nullptr;
size_t i = 2;
if (!default_) {
init = parseExpression(*s[i++]);
}
auto* size = parseExpression(*s[i++]);
return Builder(wasm).makeArrayNew(heapType, size, init);
}
Expression* SExpressionWasmBuilder::makeArrayNewData(Element& s) {
auto heapType = parseHeapType(*s[1]);
Name seg = getDataSegmentName(*s[2]);
Expression* offset = parseExpression(*s[3]);
Expression* size = parseExpression(*s[4]);
return Builder(wasm).makeArrayNewData(heapType, seg, offset, size);
}
Expression* SExpressionWasmBuilder::makeArrayNewElem(Element& s) {
auto heapType = parseHeapType(*s[1]);
Name seg = getElemSegmentName(*s[2]);
Expression* offset = parseExpression(*s[3]);
Expression* size = parseExpression(*s[4]);
return Builder(wasm).makeArrayNewElem(heapType, seg, offset, size);
}
Expression* SExpressionWasmBuilder::makeArrayNewFixed(Element& s) {
auto heapType = parseHeapType(*s[1]);
size_t i = 2;
std::vector<Expression*> values;
if (i < s.size() && s[i]->isStr()) {
// With the standard syntax one should specify explicitly the size
// of the array
if ((size_t)parseIndex(*s[i]) != s.size() - 3) {
throw ParseException(
"wrong number of elements in array: "s + s.toString(), s.line, s.col);
}
i++;
}
while (i < s.size()) {
values.push_back(parseExpression(*s[i++]));
}
return Builder(wasm).makeArrayNewFixed(heapType, values);
}
Expression* SExpressionWasmBuilder::makeArrayGet(Element& s, bool signed_) {
auto heapType = parseHeapType(*s[1]);
if (!heapType.isArray()) {
throw ParseException(
"bad array heap type: "s + s.toString(), s.line, s.col);
}
auto ref = parseExpression(*s[2]);
auto type = heapType.getArray().element.type;
validateHeapTypeUsingChild(ref, heapType, s);
auto index = parseExpression(*s[3]);
return Builder(wasm).makeArrayGet(ref, index, type, signed_);
}
Expression* SExpressionWasmBuilder::makeArraySet(Element& s) {
auto heapType = parseHeapType(*s[1]);
auto ref = parseExpression(*s[2]);
validateHeapTypeUsingChild(ref, heapType, s);
auto index = parseExpression(*s[3]);
auto value = parseExpression(*s[4]);
return Builder(wasm).makeArraySet(ref, index, value);
}
Expression* SExpressionWasmBuilder::makeArrayLen(Element& s) {
// There may or may not be a type annotation.
Index i = 1;
try {
parseHeapType(*s[i]);
++i;
} catch (...) {
}
auto ref = parseExpression(*s[i]);
return Builder(wasm).makeArrayLen(ref);
}
Expression* SExpressionWasmBuilder::makeArrayCopy(Element& s) {
auto destHeapType = parseHeapType(*s[1]);
auto srcHeapType = parseHeapType(*s[2]);
auto destRef = parseExpression(*s[3]);
validateHeapTypeUsingChild(destRef, destHeapType, s);
auto destIndex = parseExpression(*s[4]);
auto srcRef = parseExpression(*s[5]);
validateHeapTypeUsingChild(srcRef, srcHeapType, s);
auto srcIndex = parseExpression(*s[6]);
auto length = parseExpression(*s[7]);
return Builder(wasm).makeArrayCopy(
destRef, destIndex, srcRef, srcIndex, length);
}
Expression* SExpressionWasmBuilder::makeArrayFill(Element& s) {
auto heapType = parseHeapType(*s[1]);
auto ref = parseExpression(*s[2]);
validateHeapTypeUsingChild(ref, heapType, s);
auto index = parseExpression(*s[3]);
auto value = parseExpression(*s[4]);
auto size = parseExpression(*s[5]);
return Builder(wasm).makeArrayFill(ref, index, value, size);
}
Expression* SExpressionWasmBuilder::makeArrayInitData(Element& s) {
auto heapType = parseHeapType(*s[1]);
auto seg = getDataSegmentName(*s[2]);
auto ref = parseExpression(*s[3]);
validateHeapTypeUsingChild(ref, heapType, s);
auto index = parseExpression(*s[4]);
auto offset = parseExpression(*s[5]);
auto size = parseExpression(*s[6]);
return Builder(wasm).makeArrayInitData(seg, ref, index, offset, size);
}
Expression* SExpressionWasmBuilder::makeArrayInitElem(Element& s) {
auto heapType = parseHeapType(*s[1]);
auto seg = getElemSegmentName(*s[2]);
auto ref = parseExpression(*s[3]);
validateHeapTypeUsingChild(ref, heapType, s);
auto index = parseExpression(*s[4]);
auto offset = parseExpression(*s[5]);
auto size = parseExpression(*s[6]);
return Builder(wasm).makeArrayInitElem(seg, ref, index, offset, size);
}
Expression* SExpressionWasmBuilder::makeRefAs(Element& s, RefAsOp op) {
auto* value = parseExpression(s[1]);
if (!value->type.isRef() && value->type != Type::unreachable) {
throw ParseException(
"ref.as child must be a ref: "s + s.toString(), s.line, s.col);
}
return Builder(wasm).makeRefAs(op, value);
}
Expression*
SExpressionWasmBuilder::makeStringNew(Element& s, StringNewOp op, bool try_) {
size_t i = 1;
Expression* length = nullptr;
if (op == StringNewWTF8) {
if (s[i]->isStr()) {
// legacy syntax
std::string_view str = s[i++]->str().str;
if (str == "utf8") {
op = StringNewUTF8;
} else if (str == "wtf8") {
op = StringNewWTF8;
} else if (str == "replace") {
op = StringNewLossyUTF8;
} else {
throw ParseException(
"bad string.new op: "s + s.toString(), s.line, s.col);
}
}
length = parseExpression(s[i + 1]);
return Builder(wasm).makeStringNew(op, parseExpression(s[i]), length, try_);
} else if (op == StringNewUTF8 || op == StringNewLossyUTF8 ||
op == StringNewWTF16) {
length = parseExpression(s[i + 1]);
return Builder(wasm).makeStringNew(op, parseExpression(s[i]), length, try_);
} else if (op == StringNewWTF8Array) {
if (s[i]->isStr()) {
// legacy syntax
std::string_view str = s[i++]->str().str;
if (str == "utf8") {
op = StringNewUTF8Array;
} else if (str == "wtf8") {
op = StringNewWTF8Array;
} else if (str == "replace") {
op = StringNewLossyUTF8Array;
} else {
throw ParseException(
"bad string.new op: "s + s.toString(), s.line, s.col);
}
}
auto* start = parseExpression(s[i + 1]);
auto* end = parseExpression(s[i + 2]);
return Builder(wasm).makeStringNew(
op, parseExpression(s[i]), start, end, try_);
} else if (op == StringNewUTF8Array || op == StringNewLossyUTF8Array ||
op == StringNewWTF16Array) {
auto* start = parseExpression(s[i + 1]);
auto* end = parseExpression(s[i + 2]);
return Builder(wasm).makeStringNew(
op, parseExpression(s[i]), start, end, try_);
} else if (op == StringNewFromCodePoint) {
return Builder(wasm).makeStringNew(
op, parseExpression(s[i]), nullptr, try_);
} else {
throw ParseException("bad string.new op: "s + s.toString(), s.line, s.col);
}
}
Expression* SExpressionWasmBuilder::makeStringConst(Element& s) {
std::vector<char> data;
stringToBinary(*s[1], s[1]->str().str, data);
Name str = std::string_view(data.data(), data.size());
return Builder(wasm).makeStringConst(str);
}
Expression* SExpressionWasmBuilder::makeStringMeasure(Element& s,
StringMeasureOp op) {
size_t i = 1;
if (op == StringMeasureWTF8 && s[i]->isStr()) {
// legacy syntax
std::string_view str = s[i++]->str().str;
if (str == "utf8") {
op = StringMeasureUTF8;
} else if (str == "wtf8") {
op = StringMeasureWTF8;
} else {
throw ParseException(
"bad string.measure op: "s + s.toString(), s.line, s.col);
}
}
return Builder(wasm).makeStringMeasure(op, parseExpression(s[i]));
}
Expression* SExpressionWasmBuilder::makeStringEncode(Element& s,
StringEncodeOp op) {
size_t i = 1;
Expression* start = nullptr;
if (op == StringEncodeWTF8) {
if (s[i]->isStr()) {
// legacy syntax
std::string_view str = s[i++]->str().str;
if (str == "utf8") {
op = StringEncodeUTF8;
} else if (str == "replace") {
op = StringEncodeLossyUTF8;
} else if (str == "wtf8") {
op = StringEncodeWTF8;
} else {
throw ParseException(
"bad string.new op: "s + s.toString(), s.line, s.col);
}
}
} else if (op == StringEncodeWTF8Array) {
if (s[i]->isStr()) {
// legacy syntax
std::string_view str = s[i++]->str().str;
if (str == "utf8") {
op = StringEncodeUTF8Array;
} else if (str == "replace") {
op = StringEncodeLossyUTF8Array;
} else if (str == "wtf8") {
op = StringEncodeWTF8Array;
} else {
throw ParseException(
"bad string.new op: "s + s.toString(), s.line, s.col);
}
}
start = parseExpression(s[i + 2]);
} else if (op == StringEncodeUTF8Array || op == StringEncodeLossyUTF8Array ||
op == StringEncodeWTF16Array) {
start = parseExpression(s[i + 2]);
}
return Builder(wasm).makeStringEncode(
op, parseExpression(s[i]), parseExpression(s[i + 1]), start);
}
Expression* SExpressionWasmBuilder::makeStringConcat(Element& s) {
return Builder(wasm).makeStringConcat(parseExpression(s[1]),
parseExpression(s[2]));
}
Expression* SExpressionWasmBuilder::makeStringEq(Element& s, StringEqOp op) {
return Builder(wasm).makeStringEq(
op, parseExpression(s[1]), parseExpression(s[2]));
}
Expression* SExpressionWasmBuilder::makeStringAs(Element& s, StringAsOp op) {
return Builder(wasm).makeStringAs(op, parseExpression(s[1]));
}
Expression* SExpressionWasmBuilder::makeStringWTF8Advance(Element& s) {
return Builder(wasm).makeStringWTF8Advance(
parseExpression(s[1]), parseExpression(s[2]), parseExpression(s[3]));
}
Expression* SExpressionWasmBuilder::makeStringWTF16Get(Element& s) {
return Builder(wasm).makeStringWTF16Get(parseExpression(s[1]),
parseExpression(s[2]));
}
Expression* SExpressionWasmBuilder::makeStringIterNext(Element& s) {
return Builder(wasm).makeStringIterNext(parseExpression(s[1]));
}
Expression* SExpressionWasmBuilder::makeStringIterMove(Element& s,
StringIterMoveOp op) {
return Builder(wasm).makeStringIterMove(
op, parseExpression(s[1]), parseExpression(s[2]));
}
Expression* SExpressionWasmBuilder::makeStringSliceWTF(Element& s,
StringSliceWTFOp op) {
return Builder(wasm).makeStringSliceWTF(
op, parseExpression(s[1]), parseExpression(s[2]), parseExpression(s[3]));
}
Expression* SExpressionWasmBuilder::makeStringSliceIter(Element& s) {
return Builder(wasm).makeStringSliceIter(parseExpression(s[1]),
parseExpression(s[2]));
}
// converts an s-expression string representing binary data into an output
// sequence of raw bytes this appends to data, which may already contain
// content.
void SExpressionWasmBuilder::stringToBinary(Element& s,
std::string_view str,
std::vector<char>& data) {
auto originalSize = data.size();
data.resize(originalSize + str.size());
char* write = data.data() + originalSize;
const char* end = str.data() + str.size();
for (const char* input = str.data(); input < end;) {
if (input[0] == '\\') {
if (input + 1 >= end) {
throw ParseException(
"Unterminated escape sequence: "s + s.toString(), s.line, s.col);
}
if (input[1] == 't') {
*write++ = '\t';
input += 2;
continue;
} else if (input[1] == 'n') {
*write++ = '\n';
input += 2;
continue;
} else if (input[1] == 'r') {
*write++ = '\r';
input += 2;
continue;
} else if (input[1] == '"') {
*write++ = '"';
input += 2;
continue;
} else if (input[1] == '\'') {
*write++ = '\'';
input += 2;
continue;
} else if (input[1] == '\\') {
*write++ = '\\';
input += 2;
continue;
} else {
if (input + 2 >= end) {
throw ParseException(
"Unterminated escape sequence: "s + s.toString(), s.line, s.col);
}
*write++ = (char)(unhex(input[1]) * 16 + unhex(input[2]));
input += 3;
continue;
}
}
*write++ = input[0];
input++;
}
assert(write >= data.data());
size_t actual = write - data.data();
assert(actual <= data.size());
data.resize(actual);
}
Index SExpressionWasmBuilder::parseMemoryIndex(
Element& s, Index i, std::unique_ptr<Memory>& memory) {
if (i < s.size() && s[i]->isStr()) {
if (s[i]->str() == "i64") {
i++;
memory->indexType = Type::i64;
} else if (s[i]->str() == "i32") {
i++;
memory->indexType = Type::i32;
}
}
return i;
}
Index SExpressionWasmBuilder::parseMemoryLimits(
Element& s, Index i, std::unique_ptr<Memory>& memory) {
i = parseMemoryIndex(s, i, memory);
if (i == s.size()) {
throw ParseException(
"missing memory limits: "s + s.toString(), s.line, s.col);
}
auto initElem = s[i++];
memory->initial = getAddress(initElem);
if (!memory->is64()) {
checkAddress(memory->initial, "excessive memory init", initElem);
}
if (i == s.size()) {
memory->max = Memory::kUnlimitedSize;
} else {
auto maxElem = s[i++];
memory->max = getAddress(maxElem);
if (!memory->is64() && memory->max > Memory::kMaxSize32) {
throw ParseException("total memory must be <= 4GB: "s + s.toString(),
maxElem->line,
maxElem->col);
}
}
return i;
}
void SExpressionWasmBuilder::parseMemory(Element& s, bool preParseImport) {
auto memory = std::make_unique<Memory>();
memory->shared = false;
Index i = 1;
if (s[i]->dollared()) {
memory->setExplicitName(s[i++]->str());
} else {
memory->name = Name::fromInt(memoryCounter++);
}
memoryNames.push_back(memory->name);
i = parseMemoryIndex(s, i, memory);
Name importModule, importBase;
if (s[i]->isList()) {
auto& inner = *s[i];
if (elementStartsWith(inner, EXPORT)) {
auto ex = std::make_unique<Export>();
ex->name = inner[1]->str();
ex->value = memory->name;
ex->kind = ExternalKind::Memory;
if (wasm.getExportOrNull(ex->name)) {
throw ParseException(
"duplicate export: "s + inner.toString(), inner.line, inner.col);
}
wasm.addExport(ex.release());
i++;
} else if (elementStartsWith(inner, IMPORT)) {
memory->module = inner[1]->str();
memory->base = inner[2]->str();
i++;
} else if (elementStartsWith(inner, SHARED)) {
memory->shared = true;
parseMemoryLimits(inner, 1, memory);
i++;
} else {
if (!(inner.size() > 0 ? inner[0]->str() != IMPORT : true)) {
throw ParseException(
"bad import ending: "s + inner.toString(), inner.line, inner.col);
}
// (memory (data ..)) format
auto j = parseMemoryIndex(inner, 1, memory);
auto offset = allocator.alloc<Const>();
if (memory->is64()) {
offset->set(Literal(int64_t(0)));
} else {
offset->set(Literal(int32_t(0)));
}
auto segName = Name::fromInt(dataCounter++);
auto seg = Builder::makeDataSegment(segName, memory->name, false, offset);
dataSegmentNames.push_back(segName);
parseInnerData(inner, j, seg);
memory->initial = seg->data.size();
wasm.addDataSegment(std::move(seg));
wasm.addMemory(std::move(memory));
return;
}
}
if (!memory->shared) {
i = parseMemoryLimits(s, i, memory);
}
// Parse memory initializers.
while (i < s.size()) {
Element& curr = *s[i];
size_t j = 1;
Address offsetValue;
if (elementStartsWith(curr, DATA)) {
offsetValue = 0;
} else {
auto offsetElem = curr[j++];
offsetValue = getAddress(offsetElem);
if (!memory->is64()) {
checkAddress(offsetValue, "excessive memory offset", offsetElem);
}
}
std::string_view input = curr[j]->str().str;
auto* offset = allocator.alloc<Const>();
if (memory->is64()) {
offset->type = Type::i64;
offset->value = Literal(offsetValue);
} else {
offset->type = Type::i32;
offset->value = Literal(int32_t(offsetValue));
}
if (input.size()) {
std::vector<char> data;
stringToBinary(*curr[j], input, data);
auto segment = Builder::makeDataSegment(Name::fromInt(dataCounter++),
memory->name,
false,
offset,
data.data(),
data.size());
segment->hasExplicitName = false;
dataSegmentNames.push_back(segment->name);
wasm.addDataSegment(std::move(segment));
} else {
auto segment = Builder::makeDataSegment(
Name::fromInt(dataCounter++), memory->name, false, offset);
segment->hasExplicitName = false;
wasm.addDataSegment(std::move(segment));
}
i++;
}
wasm.addMemory(std::move(memory));
}
void SExpressionWasmBuilder::parseData(Element& s) {
Index i = 1;
Name name = Name::fromInt(dataCounter++);
bool hasExplicitName = false;
Name memory;
bool isPassive = true;
Expression* offset = nullptr;
if (s[i]->isStr() && s[i]->dollared()) {
name = s[i++]->str();
hasExplicitName = true;
}
dataSegmentNames.push_back(name);
if (s[i]->isList()) {
// Optional (memory <memoryidx>)
if (elementStartsWith(s[i], MEMORY)) {
auto& inner = *s[i++];
memory = getMemoryName(*inner[1]);
} else {
memory = getMemoryNameAtIdx(0);
}
// Offset expression (offset (<expr>)) | (<expr>)
auto& inner = *s[i++];
if (elementStartsWith(inner, OFFSET)) {
offset = parseExpression(inner[1]);
} else {
offset = parseExpression(inner);
}
isPassive = false;
}
auto seg = Builder::makeDataSegment(name, memory, isPassive, offset);
seg->hasExplicitName = hasExplicitName;
parseInnerData(s, i, seg);
wasm.addDataSegment(std::move(seg));
}
void SExpressionWasmBuilder::parseInnerData(Element& s,
Index i,
std::unique_ptr<DataSegment>& seg) {
std::vector<char> data;
while (i < s.size()) {
std::string_view input = s[i++]->str().str;
stringToBinary(s, input, data);
}
seg->data.resize(data.size());
std::copy_n(data.data(), data.size(), seg->data.begin());
}
void SExpressionWasmBuilder::parseExport(Element& s) {
std::unique_ptr<Export> ex = std::make_unique<Export>();
ex->name = s[1]->str();
if (s[2]->isList()) {
auto& inner = *s[2];
if (elementStartsWith(inner, FUNC)) {
ex->kind = ExternalKind::Function;
ex->value = getFunctionName(*inner[1]);
} else if (elementStartsWith(inner, MEMORY)) {
ex->kind = ExternalKind::Memory;
ex->value = inner[1]->str();
} else if (elementStartsWith(inner, TABLE)) {
ex->kind = ExternalKind::Table;
ex->value = getTableName(*inner[1]);
} else if (elementStartsWith(inner, GLOBAL)) {
ex->kind = ExternalKind::Global;
ex->value = getGlobalName(*inner[1]);
} else if (inner[0]->str() == TAG) {
ex->kind = ExternalKind::Tag;
ex->value = getTagName(*inner[1]);
} else {
throw ParseException(
"invalid export: "s + inner.toString(), inner.line, inner.col);
}
} else {
// function
ex->value = s[2]->str();
ex->kind = ExternalKind::Function;
}
if (wasm.getExportOrNull(ex->name)) {
throw ParseException(
"duplicate export: "s + ex->name.toString(), s.line, s.col);
}
wasm.addExport(ex.release());
}
void SExpressionWasmBuilder::parseImport(Element& s) {
size_t i = 1;
// (import "env" "STACKTOP" (global $stackTop i32))
bool newStyle = s.size() == 4 && s[3]->isList();
auto kind = ExternalKind::Invalid;
if (newStyle) {
if (elementStartsWith(*s[3], FUNC)) {
kind = ExternalKind::Function;
} else if (elementStartsWith(*s[3], MEMORY)) {
kind = ExternalKind::Memory;
} else if (elementStartsWith(*s[3], TABLE)) {
kind = ExternalKind::Table;
} else if (elementStartsWith(*s[3], GLOBAL)) {
kind = ExternalKind::Global;
} else if ((*s[3])[0]->str() == TAG) {
kind = ExternalKind::Tag;
} else {
newStyle = false; // either (param..) or (result..)
}
}
Index newStyleInner = 1;
Name name;
if (s.size() > 3 && s[3]->isStr()) {
name = s[i++]->str();
} else if (newStyle && newStyleInner < s[3]->size() &&
(*s[3])[newStyleInner]->dollared()) {
name = (*s[3])[newStyleInner++]->str();
}
bool hasExplicitName = name.is();
if (!hasExplicitName) {
if (kind == ExternalKind::Function) {
name = Name("fimport$" + std::to_string(functionCounter++));
functionNames.push_back(name);
} else if (kind == ExternalKind::Global) {
// Handled in `parseGlobal`.
} else if (kind == ExternalKind::Memory) {
name = Name("mimport$" + std::to_string(memoryCounter++));
} else if (kind == ExternalKind::Table) {
name = Name("timport$" + std::to_string(tableCounter++));
} else if (kind == ExternalKind::Tag) {
name = Name("eimport$" + std::to_string(tagCounter++));
tagNames.push_back(name);
} else {
throw ParseException(
"invalid import: "s + s.toString(), s[3]->line, s[3]->col);
}
}
if (!newStyle) {
kind = ExternalKind::Function;
}
auto module = s[i++]->str();
if (!s[i]->isStr()) {
throw ParseException(
"no name for import: "s + s.toString(), s[i]->line, s[i]->col);
}
auto base = s[i]->str();
if (!module.size() || !base.size()) {
throw ParseException("imports must have module and base: "s + s.toString(),
s[i]->line,
s[i]->col);
}
i++;
// parse internals
Element& inner = newStyle ? *s[3] : s;
Index j = newStyle ? newStyleInner : i;
if (kind == ExternalKind::Function) {
auto func = std::make_unique<Function>();
j = parseTypeUse(inner, j, func->type);
func->setName(name, hasExplicitName);
func->module = module;
func->base = base;
functionTypes[name] = func->type;
wasm.addFunction(func.release());
} else if (kind == ExternalKind::Global) {
parseGlobal(inner, true);
j++;
auto& global = wasm.globals.back();
global->module = module;
global->base = base;
} else if (kind == ExternalKind::Table) {
auto table = std::make_unique<Table>();
table->setName(name, hasExplicitName);
table->module = module;
table->base = base;
tableNames.push_back(name);
if (j < inner.size() - 1) {
auto initElem = inner[j++];
table->initial = getAddress(initElem);
checkAddress(table->initial, "excessive table init size", initElem);
}
if (j < inner.size() - 1) {
auto maxElem = inner[j++];
table->max = getAddress(maxElem);
checkAddress(table->max, "excessive table max size", maxElem);
} else {
table->max = Table::kUnlimitedSize;
}
wasm.addTable(std::move(table));
j++; // funcref
// ends with the table element type
} else if (kind == ExternalKind::Memory) {
auto memory = std::make_unique<Memory>();
memory->setName(name, hasExplicitName);
memory->module = module;
memory->base = base;
memoryNames.push_back(name);
if (inner[j]->isList()) {
auto& limits = *inner[j];
if (!elementStartsWith(limits, SHARED)) {
throw ParseException("bad memory limit declaration: "s + s.toString(),
inner[j]->line,
inner[j]->col);
}
memory->shared = true;
j = parseMemoryLimits(limits, 1, memory);
} else {
j = parseMemoryLimits(inner, j, memory);
}
wasm.addMemory(std::move(memory));
} else if (kind == ExternalKind::Tag) {
auto tag = std::make_unique<Tag>();
HeapType tagType;
j = parseTypeUse(inner, j, tagType);
tag->sig = tagType.getSignature();
tag->setName(name, hasExplicitName);
tag->module = module;
tag->base = base;
wasm.addTag(tag.release());
}
// If there are more elements, they are invalid
if (j < inner.size()) {
throw ParseException(
"invalid element: "s + s.toString(), inner[j]->line, inner[j]->col);
}
}
void SExpressionWasmBuilder::parseGlobal(Element& s, bool preParseImport) {
std::unique_ptr<Global> global = std::make_unique<Global>();
size_t i = 1;
if (s[i]->dollared()) {
global->setExplicitName(s[i++]->str());
} else if (preParseImport) {
global->name = Name("gimport$" + std::to_string(globalCounter));
} else {
global->name = Name::fromInt(globalCounter);
}
globalCounter++;
globalNames.push_back(global->name);
bool mutable_ = false;
Type type = Type::none;
Name importModule, importBase;
while (i < s.size() && s[i]->isList()) {
auto& inner = *s[i++];
if (elementStartsWith(inner, EXPORT)) {
auto ex = std::make_unique<Export>();
ex->name = inner[1]->str();
ex->value = global->name;
ex->kind = ExternalKind::Global;
if (wasm.getExportOrNull(ex->name)) {
throw ParseException(
"duplicate export: "s + ex->name.toString(), s.line, s.col);
}
wasm.addExport(ex.release());
} else if (elementStartsWith(inner, IMPORT)) {
importModule = inner[1]->str();
importBase = inner[2]->str();
} else if (elementStartsWith(inner, MUT)) {
mutable_ = true;
type = elementToType(*inner[1]);
break;
} else {
type = elementToType(inner);
break;
}
}
if (type == Type::none) {
type = stringToType(s[i++]->str());
}
if (importModule.is()) {
// this is an import, actually
if (!importBase.size()) {
throw ParseException(
"module but no base for import "s + s.toString(), s.line, s.col);
}
if (!preParseImport) {
throw ParseException(
"!preParseImport in global: "s + s.toString(), s.line, s.col);
}
auto im = std::make_unique<Global>();
im->name = global->name;
im->module = importModule;
im->base = importBase;
im->type = type;
im->mutable_ = mutable_;
if (wasm.getGlobalOrNull(im->name)) {
throw ParseException(
"duplicate import: "s + im->name.toString(), s.line, s.col);
}
wasm.addGlobal(im.release());
return;
}
global->type = type;
if (i < s.size()) {
global->init = parseExpression(s[i++]);
} else if (!preParseImport) {
throw ParseException(
"global without init: "s + s.toString(), s.line, s.col);
}
global->mutable_ = mutable_;
if (i != s.size()) {
throw ParseException(
"extra import elements: "s + s.toString(), s.line, s.col);
}
if (wasm.getGlobalOrNull(global->name)) {
throw ParseException("duplicate import: "s + s.toString(), s.line, s.col);
}
wasm.addGlobal(global.release());
}
void SExpressionWasmBuilder::parseTable(Element& s, bool preParseImport) {
std::unique_ptr<Table> table = std::make_unique<Table>();
Index i = 1;
if (s[i]->dollared()) {
table->setExplicitName(s[i++]->str());
} else {
table->name = Name::fromInt(tableCounter++);
}
tableNames.push_back(table->name);
Name importModule, importBase;
if (s[i]->isList()) {
auto& inner = *s[i];
if (elementStartsWith(inner, EXPORT)) {
auto ex = std::make_unique<Export>();
ex->name = inner[1]->str();
ex->value = table->name;
ex->kind = ExternalKind::Table;
if (wasm.getExportOrNull(ex->name)) {
throw ParseException(
"duplicate export: "s + inner.toString(), inner.line, inner.col);
}
wasm.addExport(ex.release());
i++;
} else if (elementStartsWith(inner, IMPORT)) {
if (!preParseImport) {
throw ParseException("!preParseImport in table: "s + inner.toString(),
inner.line,
inner.col);
}
table->module = inner[1]->str();
table->base = inner[2]->str();
i++;
} else if (!elementStartsWith(inner, REF)) {
throw ParseException(
"invalid table: "s + inner.toString(), inner.line, inner.col);
}
}
bool hasExplicitLimit = false;
if (s[i]->isStr() && String::isNumber(s[i]->toString())) {
table->initial = parseIndex(*s[i++]);
hasExplicitLimit = true;
}
if (s[i]->isStr() && String::isNumber(s[i]->toString())) {
table->max = parseIndex(*s[i++]);
}
table->type = elementToType(*s[i++]);
if (!table->type.isRef()) {
throw ParseException("Only reference types are valid for tables: "s +
s.toString());
}
if (i < s.size() && s[i]->isList()) {
if (hasExplicitLimit) {
throw ParseException(
"Table cannot have both explicit limits and an inline (elem ...): "s +
s.toString());
}
// (table type (elem ..))
parseElem(*s[i], table.get());
auto it = std::find_if(wasm.elementSegments.begin(),
wasm.elementSegments.end(),
[&](std::unique_ptr<ElementSegment>& segment) {
return segment->table == table->name;
});
if (it != wasm.elementSegments.end()) {
table->initial = table->max = it->get()->data.size();
} else {
table->initial = table->max = 0;
}
}
wasm.addTable(std::move(table));
}
// parses an elem segment
// elem ::= (elem (table tableidx)? (offset (expr)) reftype vec(item (expr)))
// | (elem reftype vec(item (expr)))
// | (elem declare reftype vec(item (expr)))
//
// abbreviation:
// (offset (expr)) ≡ (expr)
// (item (expr)) ≡ (expr)
// ϵ ≡ (table 0)
//
// funcref vec(ref.func) ≡ func vec(funcidx)
// (elem (expr) vec(funcidx)) ≡ (elem (table 0) (offset (expr)) func
// vec(funcidx))
//
void SExpressionWasmBuilder::parseElem(Element& s, Table* table) {
Index i = 1;
Name name = Name::fromInt(elemCounter++);
bool hasExplicitName = false;
bool isPassive = true;
bool usesExpressions = false;
if (table) {
Expression* offset = allocator.alloc<Const>()->set(Literal(int32_t(0)));
auto segment = std::make_unique<ElementSegment>(table->name, offset);
segment->setName(name, hasExplicitName);
elemSegmentNames.push_back(name);
parseElemFinish(s, segment, i, s[i]->isList());
return;
}
if (s[i]->isStr() && s[i]->dollared()) {
name = s[i++]->str();
hasExplicitName = true;
}
elemSegmentNames.push_back(name);
if (s[i]->isStr() && s[i]->str() == DECLARE) {
// We don't store declared segments in the IR
return;
}
auto segment = std::make_unique<ElementSegment>();
segment->setName(name, hasExplicitName);
if (s[i]->isList() && !elementStartsWith(s[i], REF)) {
// Optional (table <tableidx>)
if (elementStartsWith(s[i], TABLE)) {
auto& inner = *s[i++];
segment->table = getTableName(*inner[1]);
}
// Offset expression (offset (<expr>)) | (<expr>)
auto& inner = *s[i++];
if (elementStartsWith(inner, OFFSET)) {
if (inner.size() > 2) {
throw ParseException("Invalid offset for an element segment: "s +
s.toString(),
s.line,
s.col);
}
segment->offset = parseExpression(inner[1]);
} else {
segment->offset = parseExpression(inner);
}
isPassive = false;
}
if (i < s.size()) {
if (s[i]->isStr() && s[i]->dollared()) {
usesExpressions = false;
} else if (s[i]->isStr() && s[i]->str() == FUNC) {
usesExpressions = false;
i += 1;
} else {
segment->type = elementToType(*s[i]);
usesExpressions = true;
i += 1;
}
}
if (!isPassive && segment->table.isNull()) {
if (wasm.tables.empty()) {
throw ParseException(
"active element without table: "s + s.toString(), s.line, s.col);
}
table = wasm.tables.front().get();
segment->table = table->name;
}
// We may be post-MVP also due to type reasons or otherwise, as detected by
// the utility function for Binaryen IR.
usesExpressions =
usesExpressions || TableUtils::usesExpressions(segment.get(), &wasm);
parseElemFinish(s, segment, i, usesExpressions);
}
ElementSegment* SExpressionWasmBuilder::parseElemFinish(
Element& s,
std::unique_ptr<ElementSegment>& segment,
Index i,
bool usesExpressions) {
for (; i < s.size(); i++) {
if (!s[i]->isList()) {
// An MVP-style declaration: just a function name.
auto func = getFunctionName(*s[i]);
segment->data.push_back(
Builder(wasm).makeRefFunc(func, functionTypes[func]));
continue;
}
if (!usesExpressions) {
throw ParseException("expected an MVP-style $funcname in elem: "s +
s.toString(),
s.line,
s.col);
}
auto& inner = *s[i];
if (elementStartsWith(inner, ITEM)) {
if (inner[1]->isList()) {
// (item (ref.func $f))
segment->data.push_back(parseExpression(inner[1]));
} else {
// (item ref.func $f)
inner.list().removeAt(0);
segment->data.push_back(parseExpression(inner));
}
} else {
segment->data.push_back(parseExpression(inner));
}
}
return wasm.addElementSegment(std::move(segment));
}
HeapType SExpressionWasmBuilder::parseHeapType(Element& s) {
if (s.isStr()) {
// It's a string.
if (s.dollared()) {
auto it = typeIndices.find(s.str().toString());
if (it == typeIndices.end()) {
throw ParseException(
"unknown dollared function type: "s + s.toString(), s.line, s.col);
}
return types[it->second];
} else {
// It may be a numerical index, or it may be a built-in type name like
// "i31".
auto str = s.toString();
if (String::isNumber(str)) {
size_t offset = parseIndex(s);
if (offset >= types.size()) {
throw ParseException(
"unknown indexed function type: "s + s.toString(), s.line, s.col);
}
return types[offset];
}
return stringToHeapType(s.str(), /* prefix = */ false);
}
}
throw ParseException("invalid heap type: "s + s.toString(), s.line, s.col);
}
void SExpressionWasmBuilder::parseTag(Element& s, bool preParseImport) {
auto tag = std::make_unique<Tag>();
size_t i = 1;
// Parse name
if (s[i]->isStr() && s[i]->dollared()) {
auto& inner = *s[i++];
tag->setExplicitName(inner.str());
if (wasm.getTagOrNull(tag->name)) {
throw ParseException(
"duplicate tag: "s + inner.toString(), inner.line, inner.col);
}
} else {
tag->name = Name::fromInt(tagCounter);
assert(!wasm.getTagOrNull(tag->name));
}
tagCounter++;
tagNames.push_back(tag->name);
// Parse import, if any
if (i < s.size() && elementStartsWith(*s[i], IMPORT)) {
assert(preParseImport && "import element in non-preParseImport mode");
auto& importElem = *s[i++];
if (importElem.size() != 3) {
throw ParseException("invalid import: "s + importElem.toString(),
importElem.line,
importElem.col);
}
if (!importElem[1]->isStr() || importElem[1]->dollared()) {
throw ParseException("invalid import module name: "s +
importElem[1]->toString(),
importElem[1]->line,
importElem[1]->col);
}
if (!importElem[2]->isStr() || importElem[2]->dollared()) {
throw ParseException("invalid import base name: "s +
importElem[2]->toString(),
importElem[2]->line,
importElem[2]->col);
}
tag->module = importElem[1]->str();
tag->base = importElem[2]->str();
}
// Parse export, if any
if (i < s.size() && elementStartsWith(*s[i], EXPORT)) {
auto& exportElem = *s[i++];
if (tag->module.is()) {
throw ParseException("import and export cannot be specified together: "s +
exportElem.toString(),
exportElem.line,
exportElem.col);
}
if (exportElem.size() != 2) {
throw ParseException(
"invalid export: "s + s.toString(), exportElem.line, exportElem.col);
}
if (!exportElem[1]->isStr() || exportElem[1]->dollared()) {
throw ParseException("invalid export name: "s + exportElem.toString(),
exportElem[1]->line,
exportElem[1]->col);
}
auto ex = std::make_unique<Export>();
ex->name = exportElem[1]->str();
if (wasm.getExportOrNull(ex->name)) {
throw ParseException("duplicate export: "s + exportElem.toString(),
exportElem[1]->line,
exportElem[1]->col);
}
ex->value = tag->name;
ex->kind = ExternalKind::Tag;
wasm.addExport(ex.release());
}
// Parse typeuse
HeapType tagType;
i = parseTypeUse(s, i, tagType);
tag->sig = tagType.getSignature();
// If there are more elements, they are invalid
if (i < s.size()) {
throw ParseException(
"invalid element: "s + s.toString(), s[i]->line, s[i]->col);
}
wasm.addTag(tag.release());
}
void SExpressionWasmBuilder::validateHeapTypeUsingChild(Expression* child,
HeapType heapType,
Element& s) {
if (child->type == Type::unreachable) {
return;
}
if (!child->type.isRef() ||
!HeapType::isSubType(child->type.getHeapType(), heapType)) {
throw ParseException("bad heap type: expected " + heapType.toString() +
" but found " + child->type.toString(),
s.line,
s.col);
}
}
} // namespace wasm