summaryrefslogtreecommitdiff
path: root/src/wasm
diff options
context:
space:
mode:
Diffstat (limited to 'src/wasm')
-rw-r--r--src/wasm/wasm-binary.cpp84
-rw-r--r--src/wasm/wasm-s-parser.cpp61
-rw-r--r--src/wasm/wasm-stack.cpp101
-rw-r--r--src/wasm/wasm-type.cpp22
-rw-r--r--src/wasm/wasm-validator.cpp68
-rw-r--r--src/wasm/wasm.cpp18
6 files changed, 305 insertions, 49 deletions
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index 278a9ef44..3b48d9781 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -1087,6 +1087,11 @@ int64_t WasmBinaryBuilder::getS64LEB() {
Type WasmBinaryBuilder::getType() {
int type = getS32LEB();
+ // Single value types are negative; signature indices are non-negative
+ if (type >= 0) {
+ // TODO: Handle block input types properly
+ return signatures[type].results;
+ }
switch (type) {
// None only used for block signatures. TODO: Separate out?
case BinaryConsts::EncodedType::Empty:
@@ -1699,7 +1704,7 @@ void WasmBinaryBuilder::processExpressions() {
BYN_TRACE("== processExpressions finished\n");
return;
}
- expressionStack.push_back(curr);
+ pushExpression(curr);
if (curr->type == Type::unreachable) {
// Once we see something unreachable, we don't want to add anything else
// to the stack, as it could be stacky code that is non-representable in
@@ -1759,6 +1764,22 @@ void WasmBinaryBuilder::skipUnreachableCode() {
expressionStack = savedStack;
return;
}
+ pushExpression(curr);
+ }
+}
+
+void WasmBinaryBuilder::pushExpression(Expression* curr) {
+ if (curr->type.isMulti()) {
+ // Store tuple to local and push individual extracted values
+ Builder builder(wasm);
+ Index tuple = builder.addVar(currFunction, curr->type);
+ expressionStack.push_back(builder.makeLocalSet(tuple, curr));
+ const std::vector<Type> types = curr->type.expand();
+ for (Index i = 0; i < types.size(); ++i) {
+ expressionStack.push_back(
+ builder.makeTupleExtract(builder.makeLocalGet(tuple, curr->type), i));
+ }
+ } else {
expressionStack.push_back(curr);
}
}
@@ -1778,6 +1799,7 @@ Expression* WasmBinaryBuilder::popExpression() {
}
// the stack is not empty, and we would not be going out of the current block
auto ret = expressionStack.back();
+ assert(!ret->type.isMulti());
expressionStack.pop_back();
return ret;
}
@@ -1818,6 +1840,35 @@ Expression* WasmBinaryBuilder::popNonVoidExpression() {
return block;
}
+Expression* WasmBinaryBuilder::popTuple(size_t numElems) {
+ Builder builder(wasm);
+ std::vector<Expression*> elements;
+ elements.resize(numElems);
+ for (size_t i = 0; i < numElems; i++) {
+ auto* elem = popNonVoidExpression();
+ if (elem->type == Type::unreachable) {
+ // All the previously-popped items cannot be reached, so ignore them. We
+ // cannot continue popping because there might not be enough items on the
+ // expression stack after an unreachable expression. Any remaining
+ // elements can stay unperturbed on the stack and will be explicitly
+ // dropped by some parent call to pushBlockElements.
+ return elem;
+ }
+ elements[numElems - i - 1] = elem;
+ }
+ return Builder(wasm).makeTupleMake(std::move(elements));
+}
+
+Expression* WasmBinaryBuilder::popTypedExpression(Type type) {
+ if (type.isSingle()) {
+ return popNonVoidExpression();
+ } else if (type.isMulti()) {
+ return popTuple(type.size());
+ } else {
+ WASM_UNREACHABLE("Invalid popped type");
+ }
+}
+
void WasmBinaryBuilder::validateBinary() {
if (hasDataCount && wasm.memory.segments.size() != dataCount) {
throwError("Number of segments does not agree with DataCount section");
@@ -2386,8 +2437,8 @@ void WasmBinaryBuilder::pushBlockElements(Block* curr,
assert(start <= expressionStack.size());
// The results of this block are the last values pushed to the expressionStack
Expression* results = nullptr;
- if (type != Type::none) {
- results = popNonVoidExpression();
+ if (type.isConcrete()) {
+ results = popTypedExpression(type);
}
if (expressionStack.size() < start) {
throwError("Block requires more values than are available");
@@ -2429,7 +2480,7 @@ void WasmBinaryBuilder::visitBlock(Block* curr) {
while (1) {
curr->type = getType();
curr->name = getNextLabel();
- breakStack.push_back({curr->name, curr->type != Type::none});
+ breakStack.push_back({curr->name, curr->type});
stack.push_back(curr);
if (more() && input[pos] == BinaryConsts::Block) {
// a recursion
@@ -2454,7 +2505,7 @@ void WasmBinaryBuilder::visitBlock(Block* curr) {
size_t start = expressionStack.size();
if (last) {
// the previous block is our first-position element
- expressionStack.push_back(last);
+ pushExpression(last);
}
last = curr;
processExpressions();
@@ -2478,14 +2529,13 @@ void WasmBinaryBuilder::visitBlock(Block* curr) {
Expression* WasmBinaryBuilder::getBlockOrSingleton(Type type,
unsigned numPops) {
Name label = getNextLabel();
- breakStack.push_back(
- {label, type != Type::none && type != Type::unreachable});
+ breakStack.push_back({label, type});
auto start = expressionStack.size();
Builder builder(wasm);
for (unsigned i = 0; i < numPops; i++) {
auto* pop = builder.makePop(Type::exnref);
- expressionStack.push_back(pop);
+ pushExpression(pop);
}
processExpressions();
@@ -2529,7 +2579,7 @@ void WasmBinaryBuilder::visitLoop(Loop* curr) {
startControlFlow(curr);
curr->type = getType();
curr->name = getNextLabel();
- breakStack.push_back({curr->name, 0});
+ breakStack.push_back({curr->name, Type::none});
// find the expressions in the block, and create the body
// a loop may have a list of instructions in wasm, much like
// a block, but it only has a label at the top of the loop,
@@ -2564,8 +2614,8 @@ WasmBinaryBuilder::getBreakTarget(int32_t offset) {
if (index >= breakStack.size()) {
throwError("bad breakindex (high)");
}
- BYN_TRACE("breaktarget " << breakStack[index].name << " arity "
- << breakStack[index].arity << std::endl);
+ BYN_TRACE("breaktarget " << breakStack[index].name << " type "
+ << breakStack[index].type << std::endl);
auto& ret = breakStack[index];
// if the break is in literally unreachable code, then we will not emit it
// anyhow, so do not note that the target has breaks to it
@@ -2582,8 +2632,8 @@ void WasmBinaryBuilder::visitBreak(Break* curr, uint8_t code) {
if (code == BinaryConsts::BrIf) {
curr->condition = popNonVoidExpression();
}
- if (target.arity) {
- curr->value = popNonVoidExpression();
+ if (target.type.isConcrete()) {
+ curr->value = popTypedExpression(target.type);
}
curr->finalize();
}
@@ -2599,8 +2649,8 @@ void WasmBinaryBuilder::visitSwitch(Switch* curr) {
auto defaultTarget = getBreakTarget(getU32LEB());
curr->default_ = defaultTarget.name;
BYN_TRACE("default: " << curr->default_ << "\n");
- if (defaultTarget.arity) {
- curr->value = popNonVoidExpression();
+ if (defaultTarget.type.isConcrete()) {
+ curr->value = popTypedExpression(defaultTarget.type);
}
curr->finalize();
}
@@ -4464,8 +4514,8 @@ void WasmBinaryBuilder::visitSelect(Select* curr, uint8_t code) {
void WasmBinaryBuilder::visitReturn(Return* curr) {
BYN_TRACE("zz node: Return\n");
requireFunctionContext("return");
- if (currFunction->sig.results != Type::none) {
- curr->value = popNonVoidExpression();
+ if (currFunction->sig.results.isConcrete()) {
+ curr->value = popTypedExpression(currFunction->sig.results);
}
curr->finalize();
}
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp
index 573df9dfa..4354b747a 100644
--- a/src/wasm/wasm-s-parser.cpp
+++ b/src/wasm/wasm-s-parser.cpp
@@ -538,19 +538,34 @@ SExpressionWasmBuilder::parseParamOrLocal(Element& s, size_t& localIndex) {
name = Name::fromInt(localIndex);
}
localIndex++;
- Type type = stringToType(s[i]->str());
+ Type type;
+ if (s[i]->isStr()) {
+ type = stringToType(s[i]->str());
+ } else {
+ if (elementStartsWith(s, PARAM)) {
+ throw ParseException(
+ "params may not have tuple types", s[i]->line, s[i]->col);
+ }
+ auto& tuple = s[i]->list();
+ std::vector<Type> types;
+ for (size_t j = 0; j < s[i]->size(); ++j) {
+ types.push_back(stringToType(tuple[j]->str()));
+ }
+ type = Type(types);
+ }
namedParams.emplace_back(name, type);
}
return namedParams;
}
// Parses (result type) element. (e.g. (result i32))
-Type SExpressionWasmBuilder::parseResults(Element& s) {
+std::vector<Type> SExpressionWasmBuilder::parseResults(Element& s) {
assert(elementStartsWith(s, RESULT));
- if (s.size() != 2) {
- throw ParseException("invalid result arity", s.line, s.col);
+ std::vector<Type> types;
+ for (size_t i = 1; i < s.size(); i++) {
+ types.push_back(stringToType(s[i]->str()));
}
- return stringToType(s[1]->str());
+ return types;
}
// Parses an element that references an entry in the type section. The element
@@ -599,8 +614,8 @@ SExpressionWasmBuilder::parseTypeUse(Element& s,
while (i < s.size() && elementStartsWith(*s[i], RESULT)) {
paramsOrResultsExist = true;
- // TODO: make parseResults return a vector
- results.push_back(parseResults(*s[i++]));
+ auto newResults = parseResults(*s[i++]);
+ results.insert(results.end(), newResults.begin(), newResults.end());
}
auto inlineSig = Signature(Type(params), Type(results));
@@ -1635,14 +1650,13 @@ Type SExpressionWasmBuilder::parseOptionalResultType(Element& s, Index& i) {
return stringToType(s[i++]->str());
}
- Element& params = *s[i];
- IString id = params[0]->str();
- if (id != RESULT) {
- return Type::none;
+ Element& results = *s[i];
+ IString id = results[0]->str();
+ if (id == RESULT) {
+ i++;
+ return Type(parseResults(results));
}
-
- i++;
- return stringToType(params[1]->str());
+ return Type::none;
}
Expression* SExpressionWasmBuilder::makeLoop(Element& s) {
@@ -1882,6 +1896,21 @@ Expression* SExpressionWasmBuilder::makeBrOnExn(Element& s) {
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 = atoi(s[1]->str().c_str());
+ ret->tuple = parseExpression(s[2]);
+ ret->finalize();
+ return ret;
+}
+
// converts an s-expression string representing binary data into an output
// sequence of raw bytes this appends to data, which may already contain
// content.
@@ -2440,8 +2469,8 @@ void SExpressionWasmBuilder::parseType(Element& s) {
auto newParams = parseParamOrLocal(curr);
params.insert(params.end(), newParams.begin(), newParams.end());
} else if (elementStartsWith(curr, RESULT)) {
- // TODO: Parse multiple results at once
- results.push_back(parseResults(curr));
+ auto newResults = parseResults(curr);
+ results.insert(results.end(), newResults.begin(), newResults.end());
}
}
signatures.emplace_back(Type(params), Type(results));
diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp
index 2e4cd4344..df9ad8620 100644
--- a/src/wasm/wasm-stack.cpp
+++ b/src/wasm/wasm-stack.cpp
@@ -15,13 +15,24 @@
*/
#include "wasm-stack.h"
+#include "ir/find_all.h"
namespace wasm {
+void BinaryInstWriter::emitResultType(Type type) {
+ if (type == Type::unreachable) {
+ o << binaryType(Type::none);
+ } else if (type.isMulti()) {
+ o << U32LEB(parent.getTypeIndex(Signature(Type::none, type)));
+ } else {
+ o << binaryType(type);
+ }
+}
+
void BinaryInstWriter::visitBlock(Block* curr) {
breakStack.push_back(curr->name);
o << int8_t(BinaryConsts::Block);
- o << binaryType(curr->type != Type::unreachable ? curr->type : Type::none);
+ emitResultType(curr->type);
}
void BinaryInstWriter::visitIf(If* curr) {
@@ -30,7 +41,7 @@ void BinaryInstWriter::visitIf(If* curr) {
// instead)
breakStack.emplace_back(IMPOSSIBLE_CONTINUE);
o << int8_t(BinaryConsts::If);
- o << binaryType(curr->type != Type::unreachable ? curr->type : Type::none);
+ emitResultType(curr->type);
}
void BinaryInstWriter::emitIfElse(If* curr) {
@@ -46,7 +57,7 @@ void BinaryInstWriter::emitIfElse(If* curr) {
void BinaryInstWriter::visitLoop(Loop* curr) {
breakStack.push_back(curr->name);
o << int8_t(BinaryConsts::Loop);
- o << binaryType(curr->type != Type::unreachable ? curr->type : Type::none);
+ emitResultType(curr->type);
}
void BinaryInstWriter::visitBreak(Break* curr) {
@@ -76,13 +87,30 @@ void BinaryInstWriter::visitCallIndirect(CallIndirect* curr) {
}
void BinaryInstWriter::visitLocalGet(LocalGet* curr) {
- o << int8_t(BinaryConsts::LocalGet)
- << U32LEB(mappedLocals[std::make_pair(curr->index, 0)]);
+ size_t numValues = func->getLocalType(curr->index).size();
+ for (Index i = 0; i < numValues; ++i) {
+ o << int8_t(BinaryConsts::LocalGet)
+ << U32LEB(mappedLocals[std::make_pair(curr->index, i)]);
+ }
}
void BinaryInstWriter::visitLocalSet(LocalSet* curr) {
- o << int8_t(curr->isTee() ? BinaryConsts::LocalTee : BinaryConsts::LocalSet)
- << U32LEB(mappedLocals[std::make_pair(curr->index, 0)]);
+ size_t numValues = func->getLocalType(curr->index).size();
+ for (Index i = numValues - 1; i >= 1; --i) {
+ o << int8_t(BinaryConsts::LocalSet)
+ << U32LEB(mappedLocals[std::make_pair(curr->index, i)]);
+ }
+ if (!curr->isTee()) {
+ o << int8_t(BinaryConsts::LocalSet)
+ << U32LEB(mappedLocals[std::make_pair(curr->index, 0)]);
+ } else {
+ o << int8_t(BinaryConsts::LocalTee)
+ << U32LEB(mappedLocals[std::make_pair(curr->index, 0)]);
+ for (Index i = 1; i < numValues; ++i) {
+ o << int8_t(BinaryConsts::LocalGet)
+ << U32LEB(mappedLocals[std::make_pair(curr->index, i)]);
+ }
+ }
}
void BinaryInstWriter::visitGlobalGet(GlobalGet* curr) {
@@ -1596,7 +1624,7 @@ void BinaryInstWriter::visitRefFunc(RefFunc* curr) {
void BinaryInstWriter::visitTry(Try* curr) {
breakStack.emplace_back(IMPOSSIBLE_CONTINUE);
o << int8_t(BinaryConsts::Try);
- o << binaryType(curr->type != Type::unreachable ? curr->type : Type::none);
+ emitResultType(curr->type);
}
void BinaryInstWriter::emitCatch(Try* curr) {
@@ -1629,7 +1657,10 @@ void BinaryInstWriter::visitUnreachable(Unreachable* curr) {
}
void BinaryInstWriter::visitDrop(Drop* curr) {
- o << int8_t(BinaryConsts::Drop);
+ size_t numValues = curr->value->type.size();
+ for (size_t i = 0; i < numValues; i++) {
+ o << int8_t(BinaryConsts::Drop);
+ }
}
void BinaryInstWriter::visitPush(Push* curr) {
@@ -1640,6 +1671,30 @@ void BinaryInstWriter::visitPop(Pop* curr) {
// Turns into nothing in the binary format
}
+void BinaryInstWriter::visitTupleMake(TupleMake* curr) {
+ // Turns into nothing in the binary format
+}
+
+void BinaryInstWriter::visitTupleExtract(TupleExtract* curr) {
+ size_t numVals = curr->tuple->type.size();
+ // Drop all values after the one we want
+ for (size_t i = curr->index + 1; i < numVals; ++i) {
+ o << int8_t(BinaryConsts::Drop);
+ }
+ // If the extracted value is the only one left, we're done
+ if (curr->index == 0) {
+ return;
+ }
+ // Otherwise, save it to a scratch local, drop the others, then retrieve it
+ assert(scratchLocals.find(curr->type) != scratchLocals.end());
+ auto scratch = scratchLocals[curr->type];
+ o << int8_t(BinaryConsts::LocalSet) << U32LEB(scratch);
+ for (size_t i = 0; i < curr->index; ++i) {
+ o << int8_t(BinaryConsts::Drop);
+ }
+ o << int8_t(BinaryConsts::LocalGet) << U32LEB(scratch);
+}
+
void BinaryInstWriter::emitScopeEnd(Expression* curr) {
assert(!breakStack.empty());
breakStack.pop_back();
@@ -1667,13 +1722,14 @@ void BinaryInstWriter::mapLocalsAndEmitHeader() {
numLocalsByType[t]++;
}
}
+ countScratchLocals();
std::map<Type, size_t> currLocalsByType;
for (Index i = func->getVarIndexBase(); i < func->getNumLocals(); i++) {
const std::vector<Type> types = func->getLocalType(i).expand();
for (Index j = 0; j < types.size(); j++) {
Type type = types[j];
auto fullIndex = std::make_pair(i, j);
- size_t index = func->getVarIndexBase();
+ Index index = func->getVarIndexBase();
for (auto& typeCount : numLocalsByType) {
if (type == typeCount.first) {
mappedLocals[fullIndex] = index + currLocalsByType[typeCount.first];
@@ -1684,12 +1740,37 @@ void BinaryInstWriter::mapLocalsAndEmitHeader() {
}
}
}
+ setScratchLocals();
o << U32LEB(numLocalsByType.size());
for (auto& typeCount : numLocalsByType) {
o << U32LEB(typeCount.second) << binaryType(typeCount.first);
}
}
+void BinaryInstWriter::countScratchLocals() {
+ // Add a scratch register in `numLocalsByType` for each type of
+ // tuple.extract with nonzero index present.
+ FindAll<TupleExtract> extracts(func->body);
+ for (auto* extract : extracts.list) {
+ if (extract->type != Type::unreachable && extract->index != 0) {
+ scratchLocals[extract->type] = 0;
+ }
+ }
+ for (auto t : scratchLocals) {
+ numLocalsByType[t.first]++;
+ }
+}
+
+void BinaryInstWriter::setScratchLocals() {
+ Index index = func->getVarIndexBase();
+ for (auto& typeCount : numLocalsByType) {
+ index += typeCount.second;
+ if (scratchLocals.find(typeCount.first) != scratchLocals.end()) {
+ scratchLocals[typeCount.first] = index - 1;
+ }
+ }
+}
+
void BinaryInstWriter::emitMemoryAccess(size_t alignment,
size_t bytes,
uint32_t offset) {
diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp
index 3542f5d69..148b55716 100644
--- a/src/wasm/wasm-type.cpp
+++ b/src/wasm/wasm-type.cpp
@@ -229,7 +229,7 @@ Type Type::get(unsigned byteSize, bool float_) {
WASM_UNREACHABLE("invalid size");
}
-bool Type::Type::isSubType(Type left, Type right) {
+bool Type::isSubType(Type left, Type right) {
if (left == right) {
return true;
}
@@ -240,7 +240,7 @@ bool Type::Type::isSubType(Type left, Type right) {
return false;
}
-Type Type::Type::getLeastUpperBound(Type a, Type b) {
+Type Type::getLeastUpperBound(Type a, Type b) {
if (a == b) {
return a;
}
@@ -250,8 +250,24 @@ Type Type::Type::getLeastUpperBound(Type a, Type b) {
if (b == Type::unreachable) {
return a;
}
+ if (a.size() != b.size()) {
+ return Type::none; // a poison value that must not be consumed
+ }
+ if (a.isMulti()) {
+ std::vector<Type> types;
+ types.resize(a.size());
+ const auto& as = a.expand();
+ const auto& bs = b.expand();
+ for (size_t i = 0; i < types.size(); ++i) {
+ types[i] = getLeastUpperBound(as[i], bs[i]);
+ if (types[i] == Type::none) {
+ return Type::none;
+ }
+ }
+ return Type(types);
+ }
if (!a.isRef() || !b.isRef()) {
- return none; // a poison value that must not be consumed
+ return Type::none;
}
if (a == Type::nullref) {
return b;
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp
index 484376358..1db8ed7c0 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -319,6 +319,8 @@ public:
void visitThrow(Throw* curr);
void visitRethrow(Rethrow* curr);
void visitBrOnExn(BrOnExn* curr);
+ void visitTupleMake(TupleMake* curr);
+ void visitTupleExtract(TupleExtract* curr);
void visitFunction(Function* curr);
// helpers
@@ -386,6 +388,11 @@ void FunctionValidator::noteLabelName(Name name) {
}
void FunctionValidator::visitBlock(Block* curr) {
+ if (!getModule()->features.hasMultivalue()) {
+ shouldBeTrue(!curr->type.isMulti(),
+ curr,
+ "Multivalue block type (multivalue is not enabled)");
+ }
// if we are break'ed to, then the value must be right for us
if (curr->name.is()) {
noteLabelName(curr->name);
@@ -1744,6 +1751,14 @@ void FunctionValidator::visitSelect(Select* curr) {
curr->condition->type == Type::i32,
curr,
"select condition must be valid");
+ if (curr->ifTrue->type != Type::unreachable) {
+ shouldBeTrue(
+ curr->ifTrue->type.isSingle(), curr, "select value may not be a tuple");
+ }
+ if (curr->ifFalse->type != Type::unreachable) {
+ shouldBeTrue(
+ curr->ifFalse->type.isSingle(), curr, "select value may not be a tuple");
+ }
if (curr->type != Type::unreachable) {
shouldBeTrue(Type::isSubType(curr->ifTrue->type, curr->type),
curr,
@@ -1887,10 +1902,46 @@ void FunctionValidator::visitBrOnExn(BrOnExn* curr) {
}
}
+void FunctionValidator::visitTupleMake(TupleMake* curr) {
+ std::vector<Type> types;
+ for (auto* op : curr->operands) {
+ if (op->type == Type::unreachable) {
+ shouldBeTrue(
+ curr->type == Type::unreachable,
+ curr,
+ "If tuple.make has an unreachable operand, it must be unreachable");
+ return;
+ }
+ types.push_back(op->type);
+ }
+ shouldBeTrue(Type(types) == curr->type,
+ curr,
+ "Type of tuple.make does not match types of its operands");
+}
+
+void FunctionValidator::visitTupleExtract(TupleExtract* curr) {
+ if (curr->tuple->type == Type::unreachable) {
+ shouldBeTrue(
+ curr->type == Type::unreachable,
+ curr,
+ "If tuple.extract has an unreachable operand, it must be unreachable");
+ } else {
+ shouldBeTrue(curr->index < curr->tuple->type.size(),
+ curr,
+ "tuple.extract index out of bounds");
+ shouldBeTrue(
+ curr->type == curr->tuple->type.expand()[curr->index],
+ curr,
+ "tuple.extract type does not match the type of the extracted element");
+ }
+}
+
void FunctionValidator::visitFunction(Function* curr) {
- shouldBeTrue(!curr->sig.results.isMulti(),
- curr->body,
- "Multivalue functions not allowed yet");
+ if (curr->sig.results.isMulti()) {
+ shouldBeTrue(getModule()->features.hasMultivalue(),
+ curr->body,
+ "Multivalue function results (multivalue is not enabled)");
+ }
FeatureSet features;
for (auto type : curr->sig.params.expand()) {
features |= type.getFeatures();
@@ -2053,6 +2104,12 @@ static void validateBinaryenIR(Module& wasm, ValidationInfo& info) {
static void validateImports(Module& module, ValidationInfo& info) {
ModuleUtils::iterImportedFunctions(module, [&](Function* curr) {
+ if (curr->sig.results.isMulti()) {
+ info.shouldBeTrue(module.features.hasMultivalue(),
+ curr->name,
+ "Imported multivalue function "
+ "(multivalue is not enabled)");
+ }
if (info.validateWeb) {
for (Type param : curr->sig.params.expand()) {
info.shouldBeUnequal(param,
@@ -2252,6 +2309,11 @@ static void validateEvents(Module& module, ValidationInfo& info) {
Type(Type::none),
curr->name,
"Event type's result type should be none");
+ if (curr->sig.params.isMulti()) {
+ info.shouldBeTrue(module.features.hasMultivalue(),
+ curr->name,
+ "Multivalue event type (multivalue is not enabled)");
+ }
for (auto type : curr->sig.params.expand()) {
info.shouldBeTrue(type.isConcrete(),
curr->name,
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp
index f8874da01..64bc5615f 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -188,6 +188,10 @@ const char* getExpressionName(Expression* curr) {
return "rethrow";
case Expression::Id::BrOnExnId:
return "br_on_exn";
+ case Expression::Id::TupleMakeId:
+ return "tuple.make";
+ case Expression::Id::TupleExtractId:
+ return "tuple.extract";
case Expression::Id::NumExpressionIds:
WASM_UNREACHABLE("invalid expr id");
}
@@ -898,6 +902,20 @@ void Push::finalize() {
}
}
+void TupleMake::finalize() {
+ std::vector<Type> types;
+ for (auto* op : operands) {
+ if (op->type == Type::unreachable) {
+ type = Type::unreachable;
+ return;
+ }
+ types.push_back(op->type);
+ }
+ type = Type(types);
+}
+
+void TupleExtract::finalize() { type = tuple->type.expand()[index]; }
+
size_t Function::getNumParams() { return sig.params.size(); }
size_t Function::getNumVars() { return vars.size(); }