summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xauto_update_tests.py2
-rwxr-xr-xscripts/gen-s-parser.py5
-rw-r--r--src/gen-s-parser.inc11
-rw-r--r--src/ir/ExpressionAnalyzer.cpp4
-rw-r--r--src/ir/ExpressionManipulator.cpp10
-rw-r--r--src/ir/ReFinalize.cpp2
-rw-r--r--src/ir/effects.h2
-rw-r--r--src/ir/module-utils.h8
-rw-r--r--src/ir/utils.h4
-rw-r--r--src/passes/DeadCodeElimination.cpp4
-rw-r--r--src/passes/Precompute.cpp45
-rw-r--r--src/passes/Print.cpp46
-rw-r--r--src/support/small_vector.h5
-rw-r--r--src/wasm-binary.h7
-rw-r--r--src/wasm-builder.h13
-rw-r--r--src/wasm-interpreter.h223
-rw-r--r--src/wasm-s-parser.h4
-rw-r--r--src/wasm-stack.h30
-rw-r--r--src/wasm-traversal.h37
-rw-r--r--src/wasm.h21
-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
-rw-r--r--src/wasm2js.h8
-rw-r--r--test/binaryen.js/event.js2
-rw-r--r--test/multivalue.wast144
-rw-r--r--test/multivalue.wast.from-wast145
-rw-r--r--test/multivalue.wast.fromBinary439
-rw-r--r--test/multivalue.wast.fromBinary.noDebugInfo439
-rw-r--r--test/spec/old_func.wast14
-rw-r--r--test/unit/test_features.py76
34 files changed, 1910 insertions, 194 deletions
diff --git a/auto_update_tests.py b/auto_update_tests.py
index af585ad03..c2bcf47df 100755
--- a/auto_update_tests.py
+++ b/auto_update_tests.py
@@ -334,10 +334,10 @@ TEST_SUITES = OrderedDict([
('wasm-metadce', update_metadce_tests),
('wasm-reduce', update_reduce_tests),
('spec', update_spec_tests),
- ('binaryenjs', update_binaryen_js_tests),
('lld', lld.update_lld_tests),
('wasm2js', wasm2js.update_wasm2js_tests),
('binfmt', update_bin_fmt_tests),
+ ('binaryenjs', update_binaryen_js_tests),
])
diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py
index 3d493f1de..e290322fb 100755
--- a/scripts/gen-s-parser.py
+++ b/scripts/gen-s-parser.py
@@ -480,7 +480,10 @@ instructions = [
("try", "makeTry(s)"),
("throw", "makeThrow(s)"),
("rethrow", "makeRethrow(s)"),
- ("br_on_exn", "makeBrOnExn(s)")
+ ("br_on_exn", "makeBrOnExn(s)"),
+ # Multivalue pseudoinstructions
+ ("tuple.make", "makeTupleMake(s)"),
+ ("tuple.extract", "makeTupleExtract(s)")
]
diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc
index eab7be468..f1fd55321 100644
--- a/src/gen-s-parser.inc
+++ b/src/gen-s-parser.inc
@@ -2562,6 +2562,17 @@ switch (op[0]) {
case 'r':
if (strcmp(op, "try") == 0) { return makeTry(s); }
goto parse_error;
+ case 'u': {
+ switch (op[6]) {
+ case 'e':
+ if (strcmp(op, "tuple.extract") == 0) { return makeTupleExtract(s); }
+ goto parse_error;
+ case 'm':
+ if (strcmp(op, "tuple.make") == 0) { return makeTupleMake(s); }
+ goto parse_error;
+ default: goto parse_error;
+ }
+ }
default: goto parse_error;
}
}
diff --git a/src/ir/ExpressionAnalyzer.cpp b/src/ir/ExpressionAnalyzer.cpp
index fc9d74743..4c87bae28 100644
--- a/src/ir/ExpressionAnalyzer.cpp
+++ b/src/ir/ExpressionAnalyzer.cpp
@@ -233,6 +233,10 @@ template<typename T> void visitImmediates(Expression* curr, T& visitor) {
void visitUnreachable(Unreachable* curr) {}
void visitPush(Push* curr) {}
void visitPop(Pop* curr) {}
+ void visitTupleMake(TupleMake* curr) {}
+ void visitTupleExtract(TupleExtract* curr) {
+ visitor.visitIndex(curr->index);
+ }
} singleton(curr, visitor);
}
diff --git a/src/ir/ExpressionManipulator.cpp b/src/ir/ExpressionManipulator.cpp
index acea09bad..590d0c1ba 100644
--- a/src/ir/ExpressionManipulator.cpp
+++ b/src/ir/ExpressionManipulator.cpp
@@ -261,6 +261,16 @@ flexibleCopy(Expression* original, Module& wasm, CustomCopier custom) {
return builder.makePush(copy(curr->value));
}
Expression* visitPop(Pop* curr) { return builder.makePop(curr->type); }
+ Expression* visitTupleMake(TupleMake* curr) {
+ std::vector<Expression*> operands;
+ for (auto* op : curr->operands) {
+ operands.push_back(copy(op));
+ }
+ return builder.makeTupleMake(std::move(operands));
+ }
+ Expression* visitTupleExtract(TupleExtract* curr) {
+ return builder.makeTupleExtract(copy(curr->tuple), curr->index);
+ }
};
Copier copier(wasm, custom);
diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp
index 6d20d6597..a81182832 100644
--- a/src/ir/ReFinalize.cpp
+++ b/src/ir/ReFinalize.cpp
@@ -137,6 +137,8 @@ void ReFinalize::visitNop(Nop* curr) { curr->finalize(); }
void ReFinalize::visitUnreachable(Unreachable* curr) { curr->finalize(); }
void ReFinalize::visitPush(Push* curr) { curr->finalize(); }
void ReFinalize::visitPop(Pop* curr) { curr->finalize(); }
+void ReFinalize::visitTupleMake(TupleMake* curr) { curr->finalize(); }
+void ReFinalize::visitTupleExtract(TupleExtract* curr) { curr->finalize(); }
void ReFinalize::visitFunction(Function* curr) {
// we may have changed the body from unreachable to none, which might be bad
diff --git a/src/ir/effects.h b/src/ir/effects.h
index 9c1ed18f2..4f3af012f 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -452,6 +452,8 @@ struct EffectAnalyzer
void visitUnreachable(Unreachable* curr) { branches = true; }
void visitPush(Push* curr) { calls = true; }
void visitPop(Pop* curr) { calls = true; }
+ void visitTupleMake(TupleMake* curr) {}
+ void visitTupleExtract(TupleExtract* curr) {}
// Helpers
diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h
index 93d110120..368af71a5 100644
--- a/src/ir/module-utils.h
+++ b/src/ir/module-utils.h
@@ -418,7 +418,7 @@ template<typename T> struct CallGraphPropertyAnalysis {
}
};
-// Helper function for collecting the type signature used in a module
+// Helper function for collecting the type signatures used in a module
//
// Used when emitting or printing a module to give signatures canonical
// indices. Signatures are sorted in order of decreasing frequency to minize the
@@ -441,6 +441,12 @@ collectSignatures(Module& wasm,
TypeCounter(Counts& counts) : counts(counts) {}
void visitCallIndirect(CallIndirect* curr) { counts[curr->sig]++; }
+ void visitBlock(Block* curr) {
+ // TODO: Allow blocks to have input types as well
+ if (curr->type.isMulti()) {
+ counts[Signature(Type::none, curr->type)]++;
+ }
+ }
};
TypeCounter(counts).walk(func->body);
};
diff --git a/src/ir/utils.h b/src/ir/utils.h
index 7362cbe1a..ded7059ca 100644
--- a/src/ir/utils.h
+++ b/src/ir/utils.h
@@ -157,6 +157,8 @@ struct ReFinalize
void visitUnreachable(Unreachable* curr);
void visitPush(Push* curr);
void visitPop(Pop* curr);
+ void visitTupleMake(TupleMake* curr);
+ void visitTupleExtract(TupleExtract* curr);
void visitFunction(Function* curr);
@@ -224,6 +226,8 @@ struct ReFinalizeNode : public OverriddenVisitor<ReFinalizeNode> {
void visitUnreachable(Unreachable* curr) { curr->finalize(); }
void visitPush(Push* curr) { curr->finalize(); }
void visitPop(Pop* curr) { curr->finalize(); }
+ void visitTupleMake(TupleMake* curr) { curr->finalize(); }
+ void visitTupleExtract(TupleExtract* curr) { curr->finalize(); }
void visitExport(Export* curr) { WASM_UNREACHABLE("unimp"); }
void visitGlobal(Global* curr) { WASM_UNREACHABLE("unimp"); }
diff --git a/src/passes/DeadCodeElimination.cpp b/src/passes/DeadCodeElimination.cpp
index ac984f6cb..ebeeca364 100644
--- a/src/passes/DeadCodeElimination.cpp
+++ b/src/passes/DeadCodeElimination.cpp
@@ -365,6 +365,10 @@ struct DeadCodeElimination
DELEGATE(Rethrow);
case Expression::Id::BrOnExnId:
DELEGATE(BrOnExn);
+ case Expression::Id::TupleMakeId:
+ DELEGATE(TupleMake);
+ case Expression::Id::TupleExtractId:
+ DELEGATE(TupleExtract);
case Expression::Id::InvalidId:
WASM_UNREACHABLE("unimp");
case Expression::Id::NumExpressionIds:
diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp
index 3a886166c..197b7bc48 100644
--- a/src/passes/Precompute.cpp
+++ b/src/passes/Precompute.cpp
@@ -188,7 +188,11 @@ struct Precompute
}
// try to evaluate this into a const
Flow flow = precomputeExpression(curr);
- if (flow.value.type.isVector()) {
+ if (flow.values.size() > 1) {
+ // TODO: handle multivalue types
+ return;
+ }
+ if (flow.getSingleValue().type.isVector()) {
return;
}
if (flow.breaking()) {
@@ -199,25 +203,26 @@ struct Precompute
// this expression causes a return. if it's already a return, reuse the
// node
if (auto* ret = curr->dynCast<Return>()) {
- if (flow.value.type != Type::none) {
+ if (flow.getSingleValue().type != Type::none) {
// reuse a const value if there is one
if (ret->value) {
if (auto* value = ret->value->dynCast<Const>()) {
- value->value = flow.value;
+ value->value = flow.getSingleValue();
value->finalize();
return;
}
}
- ret->value = Builder(*getModule()).makeConstExpression(flow.value);
+ ret->value =
+ Builder(*getModule()).makeConstExpression(flow.getSingleValue());
} else {
ret->value = nullptr;
}
} else {
Builder builder(*getModule());
- replaceCurrent(
- builder.makeReturn(flow.value.type != Type::none
- ? builder.makeConstExpression(flow.value)
- : nullptr));
+ replaceCurrent(builder.makeReturn(
+ flow.getSingleValue().type != Type::none
+ ? builder.makeConstExpression(flow.getSingleValue())
+ : nullptr));
}
return;
}
@@ -226,34 +231,36 @@ struct Precompute
if (auto* br = curr->dynCast<Break>()) {
br->name = flow.breakTo;
br->condition = nullptr;
- if (flow.value.type != Type::none) {
+ if (flow.getSingleValue().type != Type::none) {
// reuse a const value if there is one
if (br->value) {
if (auto* value = br->value->dynCast<Const>()) {
- value->value = flow.value;
+ value->value = flow.getSingleValue();
value->finalize();
br->finalize();
return;
}
}
- br->value = Builder(*getModule()).makeConstExpression(flow.value);
+ br->value =
+ Builder(*getModule()).makeConstExpression(flow.getSingleValue());
} else {
br->value = nullptr;
}
br->finalize();
} else {
Builder builder(*getModule());
- replaceCurrent(
- builder.makeBreak(flow.breakTo,
- flow.value.type != Type::none
- ? builder.makeConstExpression(flow.value)
- : nullptr));
+ replaceCurrent(builder.makeBreak(
+ flow.breakTo,
+ flow.getSingleValue().type != Type::none
+ ? builder.makeConstExpression(flow.getSingleValue())
+ : nullptr));
}
return;
}
// this was precomputed
- if (flow.value.type.isConcrete()) {
- replaceCurrent(Builder(*getModule()).makeConstExpression(flow.value));
+ if (flow.getSingleValue().type.isConcrete()) {
+ replaceCurrent(
+ Builder(*getModule()).makeConstExpression(flow.getSingleValue()));
worked = true;
} else {
ExpressionManipulator::nop(curr);
@@ -292,7 +299,7 @@ private:
if (flow.breaking()) {
return Literal();
}
- return flow.value;
+ return flow.getSingleValue();
}
// Propagates values around. Returns whether we propagated.
diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp
index 5504373f5..2a483efd3 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -57,6 +57,28 @@ static std::ostream& printLocal(Index index, Function* func, std::ostream& o) {
return printName(name, o);
}
+// Unlike the default format, tuple types in s-expressions should not have
+// commas.
+struct LocalType {
+ Type type;
+ LocalType(Type type) : type(type){};
+};
+
+static std::ostream& operator<<(std::ostream& o, const LocalType& localType) {
+ Type type = localType.type;
+ if (type.isMulti()) {
+ const std::vector<Type>& types = type.expand();
+ o << '(' << types[0];
+ for (size_t i = 1; i < types.size(); ++i) {
+ o << ' ' << types[i];
+ }
+ o << ')';
+ return o;
+ }
+ o << type;
+ return o;
+}
+
// Wrapper for printing signature names
struct SigName {
Signature sig;
@@ -1387,6 +1409,11 @@ struct PrintExpressionContents
o << ".pop";
restoreNormalColor(o);
}
+ void visitTupleMake(TupleMake* curr) { printMedium(o, "tuple.make"); }
+ void visitTupleExtract(TupleExtract* curr) {
+ printMedium(o, "tuple.extract ");
+ o << curr->index;
+ }
};
// Prints an expression in s-expr format, including both the
@@ -1955,6 +1982,22 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
PrintExpressionContents(currFunction, o).visit(curr);
o << ')';
}
+ void visitTupleMake(TupleMake* curr) {
+ o << '(';
+ PrintExpressionContents(currFunction, o).visit(curr);
+ incIndent();
+ for (auto operand : curr->operands) {
+ printFullLine(operand);
+ }
+ decIndent();
+ }
+ void visitTupleExtract(TupleExtract* curr) {
+ o << '(';
+ PrintExpressionContents(currFunction, o).visit(curr);
+ incIndent();
+ printFullLine(curr->tuple);
+ decIndent();
+ }
// Module-level visitors
void handleSignature(Signature curr, Name* funcName = nullptr) {
o << "(func";
@@ -2093,7 +2136,8 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
doIndent(o, indent);
o << '(';
printMinor(o, "local ");
- printLocal(i, currFunction, o) << ' ' << curr->getLocalType(i) << ')';
+ printLocal(i, currFunction, o)
+ << ' ' << LocalType(curr->getLocalType(i)) << ')';
o << maybeNewLine;
}
// Print the body.
diff --git a/src/support/small_vector.h b/src/support/small_vector.h
index d4ad961a7..ae2a9a3a7 100644
--- a/src/support/small_vector.h
+++ b/src/support/small_vector.h
@@ -41,6 +41,11 @@ public:
using value_type = T;
SmallVector() {}
+ SmallVector(std::initializer_list<T> init) {
+ for (T item : init) {
+ push_back(item);
+ }
+ }
T& operator[](size_t i) {
return const_cast<T&>(static_cast<const SmallVector<T, N>&>(*this)[i]);
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index ce7feeb04..67211ea7d 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -1179,8 +1179,8 @@ public:
struct BreakTarget {
Name name;
- int arity;
- BreakTarget(Name name, int arity) : name(name), arity(arity) {}
+ Type type;
+ BreakTarget(Name name, Type type) : name(name), type(type) {}
};
std::vector<BreakTarget> breakStack;
// the names that breaks target. this lets us know if a block has breaks to it
@@ -1226,8 +1226,11 @@ public:
void processExpressions();
void skipUnreachableCode();
+ void pushExpression(Expression* curr);
Expression* popExpression();
Expression* popNonVoidExpression();
+ Expression* popTuple(size_t numElems);
+ Expression* popTypedExpression(Type type);
void validateBinary(); // validations that cannot be performed on the Module
void processFunctions();
diff --git a/src/wasm-builder.h b/src/wasm-builder.h
index cc31f5200..1acdc40c8 100644
--- a/src/wasm-builder.h
+++ b/src/wasm-builder.h
@@ -601,6 +601,19 @@ public:
ret->finalize();
return ret;
}
+ TupleMake* makeTupleMake(std::vector<Expression*>&& operands) {
+ auto* ret = allocator.alloc<TupleMake>();
+ ret->operands.set(operands);
+ ret->finalize();
+ return ret;
+ }
+ TupleExtract* makeTupleExtract(Expression* tuple, Index index) {
+ auto* ret = allocator.alloc<TupleExtract>();
+ ret->tuple = tuple;
+ ret->index = index;
+ ret->finalize();
+ return ret;
+ }
// Additional helpers
diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h
index cbed38b2f..153453f2b 100644
--- a/src/wasm-interpreter.h
+++ b/src/wasm-interpreter.h
@@ -49,13 +49,19 @@ extern Name WASM, RETURN_FLOW;
// in control flow.
class Flow {
public:
- Flow() = default;
- Flow(Literal value) : value(value) {}
- Flow(Name breakTo) : breakTo(breakTo) {}
+ Flow() : values{Literal()} {}
+ Flow(Literal value) : values{value} {}
+ Flow(Name breakTo) : values{Literal()}, breakTo(breakTo) {}
- Literal value;
+ SmallVector<Literal, 1> values;
Name breakTo; // if non-null, a break is going on
+ // A helper function for the common case where there is only one value
+ Literal& getSingleValue() {
+ assert(values.size() == 1);
+ return values[0];
+ }
+
bool breaking() { return breakTo.is(); }
void clearIf(Name target) {
@@ -65,8 +71,14 @@ public:
}
friend std::ostream& operator<<(std::ostream& o, Flow& flow) {
- o << "(flow " << (flow.breakTo.is() ? flow.breakTo.str : "-") << " : "
- << flow.value << ')';
+ o << "(flow " << (flow.breakTo.is() ? flow.breakTo.str : "-") << " : {";
+ for (size_t i = 0; i < flow.values.size(); ++i) {
+ if (i > 0) {
+ o << ", ";
+ }
+ o << flow.values[i];
+ }
+ o << "})";
return o;
}
};
@@ -131,6 +143,21 @@ protected:
Index depth = 0;
+ Flow generateArguments(const ExpressionList& operands,
+ LiteralList& arguments) {
+ NOTE_ENTER_("generateArguments");
+ arguments.reserve(operands.size());
+ for (auto expression : operands) {
+ Flow flow = this->visit(expression);
+ if (flow.breaking()) {
+ return flow;
+ }
+ NOTE_EVAL1(flow.getSingleValue());
+ arguments.push_back(flow.getSingleValue());
+ }
+ return Flow();
+ }
+
public:
ExpressionRunner(Index maxDepth) : maxDepth(maxDepth) {}
@@ -141,15 +168,15 @@ public:
}
auto ret = OverriddenVisitor<SubType, Flow>::visit(curr);
if (!ret.breaking() &&
- (curr->type.isConcrete() || ret.value.type.isConcrete())) {
+ (curr->type.isConcrete() || ret.getSingleValue().type.isConcrete())) {
#if 1 // def WASM_INTERPRETER_DEBUG
- if (!Type::isSubType(ret.value.type, curr->type)) {
- std::cerr << "expected " << curr->type << ", seeing " << ret.value.type
- << " from\n"
+ if (!Type::isSubType(ret.getSingleValue().type, curr->type)) {
+ std::cerr << "expected " << curr->type << ", seeing "
+ << ret.getSingleValue().type << " from\n"
<< curr << '\n';
}
#endif
- assert(Type::isSubType(ret.value.type, curr->type));
+ assert(Type::isSubType(ret.getSingleValue().type, curr->type));
}
depth--;
return ret;
@@ -195,11 +222,11 @@ public:
if (flow.breaking()) {
return flow;
}
- NOTE_EVAL1(flow.value);
- if (flow.value.geti32()) {
+ NOTE_EVAL1(flow.getSingleValue());
+ if (flow.getSingleValue().geti32()) {
Flow flow = visit(curr->ifTrue);
if (!flow.breaking() && !curr->ifFalse) {
- flow.value = Literal(); // if_else returns a value, but if does not
+ flow = Flow(); // if_else returns a value, but if does not
}
return flow;
}
@@ -236,7 +263,7 @@ public:
if (conditionFlow.breaking()) {
return conditionFlow;
}
- condition = conditionFlow.value.getInteger() != 0;
+ condition = conditionFlow.getSingleValue().getInteger() != 0;
if (!condition) {
return flow;
}
@@ -253,20 +280,20 @@ public:
if (flow.breaking()) {
return flow;
}
- value = flow.value;
+ value = flow.getSingleValue();
NOTE_EVAL1(value);
}
flow = visit(curr->condition);
if (flow.breaking()) {
return flow;
}
- int64_t index = flow.value.getInteger();
+ int64_t index = flow.getSingleValue().getInteger();
Name target = curr->default_;
if (index >= 0 && (size_t)index < curr->targets.size()) {
target = curr->targets[(size_t)index];
}
flow.breakTo = target;
- flow.value = value;
+ flow.getSingleValue() = value;
return flow;
}
@@ -285,7 +312,7 @@ public:
if (flow.breaking()) {
return flow;
}
- Literal value = flow.value;
+ Literal value = flow.getSingleValue();
NOTE_EVAL1(value);
switch (curr->op) {
case ClzInt32:
@@ -475,12 +502,12 @@ public:
if (flow.breaking()) {
return flow;
}
- Literal left = flow.value;
+ Literal left = flow.getSingleValue();
flow = visit(curr->right);
if (flow.breaking()) {
return flow;
}
- Literal right = flow.value;
+ Literal right = flow.getSingleValue();
NOTE_EVAL2(left, right);
assert(curr->left->type.isConcrete() ? left.type == curr->left->type
: true);
@@ -860,7 +887,7 @@ public:
if (flow.breaking()) {
return flow;
}
- Literal vec = flow.value;
+ Literal vec = flow.getSingleValue();
switch (curr->op) {
case ExtractLaneSVecI8x16:
return vec.extractLaneSI8x16(curr->index);
@@ -887,12 +914,12 @@ public:
if (flow.breaking()) {
return flow;
}
- Literal vec = flow.value;
+ Literal vec = flow.getSingleValue();
flow = this->visit(curr->value);
if (flow.breaking()) {
return flow;
}
- Literal value = flow.value;
+ Literal value = flow.getSingleValue();
switch (curr->op) {
case ReplaceLaneVecI8x16:
return vec.replaceLaneI8x16(value, curr->index);
@@ -915,12 +942,12 @@ public:
if (flow.breaking()) {
return flow;
}
- Literal left = flow.value;
+ Literal left = flow.getSingleValue();
flow = this->visit(curr->right);
if (flow.breaking()) {
return flow;
}
- Literal right = flow.value;
+ Literal right = flow.getSingleValue();
return left.shuffleV8x16(right, curr->mask);
}
Flow visitSIMDTernary(SIMDTernary* curr) {
@@ -929,17 +956,17 @@ public:
if (flow.breaking()) {
return flow;
}
- Literal a = flow.value;
+ Literal a = flow.getSingleValue();
flow = this->visit(curr->b);
if (flow.breaking()) {
return flow;
}
- Literal b = flow.value;
+ Literal b = flow.getSingleValue();
flow = this->visit(curr->c);
if (flow.breaking()) {
return flow;
}
- Literal c = flow.value;
+ Literal c = flow.getSingleValue();
switch (curr->op) {
case Bitselect:
return c.bitselectV128(a, b);
@@ -954,12 +981,12 @@ public:
if (flow.breaking()) {
return flow;
}
- Literal vec = flow.value;
+ Literal vec = flow.getSingleValue();
flow = this->visit(curr->shift);
if (flow.breaking()) {
return flow;
}
- Literal shift = flow.value;
+ Literal shift = flow.getSingleValue();
switch (curr->op) {
case ShlVecI8x16:
return vec.shlI8x16(shift);
@@ -1002,8 +1029,8 @@ public:
if (condition.breaking()) {
return condition;
}
- NOTE_EVAL1(condition.value);
- return condition.value.geti32() ? ifTrue : ifFalse; // ;-)
+ NOTE_EVAL1(condition.getSingleValue());
+ return condition.getSingleValue().geti32() ? ifTrue : ifFalse; // ;-)
}
Flow visitDrop(Drop* curr) {
NOTE_ENTER("Drop");
@@ -1021,7 +1048,7 @@ public:
if (flow.breaking()) {
return flow;
}
- NOTE_EVAL1(flow.value);
+ NOTE_EVAL1(flow.getSingleValue());
}
flow.breakTo = RETURN_FLOW;
return flow;
@@ -1101,6 +1128,27 @@ public:
NOTE_ENTER("AtomicFence");
return Flow();
}
+ Flow visitTupleMake(TupleMake* curr) {
+ NOTE_ENTER("tuple.make");
+ LiteralList arguments;
+ Flow flow = generateArguments(curr->operands, arguments);
+ if (flow.breaking()) {
+ return flow;
+ }
+ for (auto arg : arguments) {
+ flow.values.push_back(arg);
+ }
+ return flow;
+ }
+ Flow visitTupleExtract(TupleExtract* curr) {
+ NOTE_ENTER("tuple.extract");
+ Flow flow = visit(curr->tuple);
+ if (flow.breaking()) {
+ return flow;
+ }
+ assert(flow.values.size() > curr->index);
+ return Flow(flow.values[curr->index]);
+ }
Flow visitCall(Call*) { WASM_UNREACHABLE("unimp"); }
Flow visitCallIndirect(CallIndirect*) { WASM_UNREACHABLE("unimp"); }
@@ -1133,7 +1181,7 @@ public:
if (flow.breaking()) {
return flow;
}
- Literal value = flow.value;
+ Literal value = flow.getSingleValue();
NOTE_EVAL1(value);
return Literal(value.type == Type::nullref);
}
@@ -1359,7 +1407,7 @@ public:
globals[global->name] =
ConstantExpressionRunner<GlobalManager>(globals, maxDepth)
.visit(global->init)
- .value;
+ .getSingleValue();
});
// initialize the rest of the external interface
@@ -1424,7 +1472,8 @@ private:
Address offset =
(uint32_t)ConstantExpressionRunner<GlobalManager>(globals, maxDepth)
.visit(segment.offset)
- .value.geti32();
+ .getSingleValue()
+ .geti32();
if (offset + segment.data.size() > wasm.table.initial) {
externalInterface->trap("invalid offset when initializing table");
}
@@ -1518,26 +1567,11 @@ private:
: ExpressionRunner<RuntimeExpressionRunner>(maxDepth), instance(instance),
scope(scope) {}
- Flow generateArguments(const ExpressionList& operands,
- LiteralList& arguments) {
- NOTE_ENTER_("generateArguments");
- arguments.reserve(operands.size());
- for (auto expression : operands) {
- Flow flow = this->visit(expression);
- if (flow.breaking()) {
- return flow;
- }
- NOTE_EVAL1(flow.value);
- arguments.push_back(flow.value);
- }
- return Flow();
- }
-
Flow visitCall(Call* curr) {
NOTE_ENTER("Call");
NOTE_NAME(curr->target);
LiteralList arguments;
- Flow flow = generateArguments(curr->operands, arguments);
+ Flow flow = this->generateArguments(curr->operands, arguments);
if (flow.breaking()) {
return flow;
}
@@ -1552,9 +1586,10 @@ private:
std::cout << "(returned to " << scope.function->name << ")\n";
#endif
// TODO: make this a proper tail call (return first)
+ // TODO: handle multivalue return_calls
if (curr->isReturn) {
Const c;
- c.value = ret.value;
+ c.value = ret.getSingleValue();
c.finalize();
Return return_;
return_.value = &c;
@@ -1565,7 +1600,7 @@ private:
Flow visitCallIndirect(CallIndirect* curr) {
NOTE_ENTER("CallIndirect");
LiteralList arguments;
- Flow flow = generateArguments(curr->operands, arguments);
+ Flow flow = this->generateArguments(curr->operands, arguments);
if (flow.breaking()) {
return flow;
}
@@ -1573,14 +1608,15 @@ private:
if (target.breaking()) {
return target;
}
- Index index = target.value.geti32();
+ Index index = target.getSingleValue().geti32();
Type type = curr->isReturn ? scope.function->sig.results : curr->type;
Flow ret = instance.externalInterface->callTable(
index, curr->sig, arguments, type, *instance.self());
// TODO: make this a proper tail call (return first)
+ // TODO: handle multivalue return_call_indirects
if (curr->isReturn) {
Const c;
- c.value = ret.value;
+ c.value = ret.getSingleValue();
c.finalize();
Return return_;
return_.value = &c;
@@ -1604,10 +1640,11 @@ private:
return flow;
}
NOTE_EVAL1(index);
- NOTE_EVAL1(flow.value);
- assert(curr->isTee() ? Type::isSubType(flow.value.type, curr->type)
- : true);
- scope.locals[index] = flow.value;
+ NOTE_EVAL1(flow.getSingleValue());
+ assert(curr->isTee()
+ ? Type::isSubType(flow.getSingleValue().type, curr->type)
+ : true);
+ scope.locals[index] = flow.getSingleValue();
return curr->isTee() ? flow : Flow();
}
@@ -1627,8 +1664,8 @@ private:
return flow;
}
NOTE_EVAL1(name);
- NOTE_EVAL1(flow.value);
- instance.globals[name] = flow.value;
+ NOTE_EVAL1(flow.getSingleValue());
+ instance.globals[name] = flow.getSingleValue();
return Flow();
}
@@ -1639,7 +1676,7 @@ private:
return flow;
}
NOTE_EVAL1(flow);
- auto addr = instance.getFinalAddress(curr, flow.value);
+ auto addr = instance.getFinalAddress(curr, flow.getSingleValue());
auto ret = instance.externalInterface->load(curr, addr);
NOTE_EVAL1(addr);
NOTE_EVAL1(ret);
@@ -1655,10 +1692,10 @@ private:
if (value.breaking()) {
return value;
}
- auto addr = instance.getFinalAddress(curr, ptr.value);
+ auto addr = instance.getFinalAddress(curr, ptr.getSingleValue());
NOTE_EVAL1(addr);
NOTE_EVAL1(value);
- instance.externalInterface->store(curr, addr, value.value);
+ instance.externalInterface->store(curr, addr, value.getSingleValue());
return Flow();
}
@@ -1673,30 +1710,30 @@ private:
return value;
}
NOTE_EVAL1(ptr);
- auto addr = instance.getFinalAddress(curr, ptr.value);
+ auto addr = instance.getFinalAddress(curr, ptr.getSingleValue());
NOTE_EVAL1(addr);
NOTE_EVAL1(value);
auto loaded = instance.doAtomicLoad(addr, curr->bytes, curr->type);
NOTE_EVAL1(loaded);
- auto computed = value.value;
+ auto computed = value.getSingleValue();
switch (curr->op) {
case Add:
- computed = computed.add(value.value);
+ computed = computed.add(value.getSingleValue());
break;
case Sub:
- computed = computed.sub(value.value);
+ computed = computed.sub(value.getSingleValue());
break;
case And:
- computed = computed.and_(value.value);
+ computed = computed.and_(value.getSingleValue());
break;
case Or:
- computed = computed.or_(value.value);
+ computed = computed.or_(value.getSingleValue());
break;
case Xor:
- computed = computed.xor_(value.value);
+ computed = computed.xor_(value.getSingleValue());
break;
case Xchg:
- computed = value.value;
+ computed = value.getSingleValue();
break;
}
instance.doAtomicStore(addr, curr->bytes, computed);
@@ -1717,14 +1754,14 @@ private:
if (replacement.breaking()) {
return replacement;
}
- auto addr = instance.getFinalAddress(curr, ptr.value);
+ auto addr = instance.getFinalAddress(curr, ptr.getSingleValue());
NOTE_EVAL1(addr);
NOTE_EVAL1(expected);
NOTE_EVAL1(replacement);
auto loaded = instance.doAtomicLoad(addr, curr->bytes, curr->type);
NOTE_EVAL1(loaded);
- if (loaded == expected.value) {
- instance.doAtomicStore(addr, curr->bytes, replacement.value);
+ if (loaded == expected.getSingleValue()) {
+ instance.doAtomicStore(addr, curr->bytes, replacement.getSingleValue());
}
return loaded;
}
@@ -1746,10 +1783,10 @@ private:
return timeout;
}
auto bytes = curr->expectedType.getByteSize();
- auto addr = instance.getFinalAddress(ptr.value, bytes);
+ auto addr = instance.getFinalAddress(ptr.getSingleValue(), bytes);
auto loaded = instance.doAtomicLoad(addr, bytes, curr->expectedType);
NOTE_EVAL1(loaded);
- if (loaded != expected.value) {
+ if (loaded != expected.getSingleValue()) {
return Literal(int32_t(1)); // not equal
}
// TODO: add threads support!
@@ -1821,7 +1858,7 @@ private:
if (flow.breaking()) {
return flow;
}
- return (flow.value.*splat)();
+ return (flow.getSingleValue().*splat)();
}
Flow visitSIMDLoadExtend(SIMDLoad* curr) {
Flow flow = this->visit(curr->ptr);
@@ -1829,7 +1866,7 @@ private:
return flow;
}
NOTE_EVAL1(flow);
- Address src(uint32_t(flow.value.geti32()));
+ Address src(uint32_t(flow.getSingleValue().geti32()));
auto loadLane = [&](Address addr) {
switch (curr->op) {
case LoadExtSVec8x8ToVecI16x8:
@@ -1890,7 +1927,7 @@ private:
return flow;
}
int32_t ret = instance.memorySize;
- uint32_t delta = flow.value.geti32();
+ uint32_t delta = flow.getSingleValue().geti32();
if (delta > uint32_t(-1) / Memory::kPageSize) {
return fail;
}
@@ -1931,9 +1968,9 @@ private:
assert(curr->segment < instance.wasm.memory.segments.size());
Memory::Segment& segment = instance.wasm.memory.segments[curr->segment];
- Address destVal(uint32_t(dest.value.geti32()));
- Address offsetVal(uint32_t(offset.value.geti32()));
- Address sizeVal(uint32_t(size.value.geti32()));
+ Address destVal(uint32_t(dest.getSingleValue().geti32()));
+ Address offsetVal(uint32_t(offset.getSingleValue().geti32()));
+ Address sizeVal(uint32_t(size.getSingleValue().geti32()));
if (offsetVal + sizeVal > 0 &&
instance.droppedSegments.count(curr->segment)) {
@@ -1975,9 +2012,9 @@ private:
NOTE_EVAL1(dest);
NOTE_EVAL1(source);
NOTE_EVAL1(size);
- Address destVal(uint32_t(dest.value.geti32()));
- Address sourceVal(uint32_t(source.value.geti32()));
- Address sizeVal(uint32_t(size.value.geti32()));
+ Address destVal(uint32_t(dest.getSingleValue().geti32()));
+ Address sourceVal(uint32_t(source.getSingleValue().geti32()));
+ Address sizeVal(uint32_t(size.getSingleValue().geti32()));
if ((uint64_t)sourceVal + sizeVal >
(uint64_t)instance.memorySize * Memory::kPageSize ||
@@ -2020,14 +2057,14 @@ private:
NOTE_EVAL1(dest);
NOTE_EVAL1(value);
NOTE_EVAL1(size);
- Address destVal(uint32_t(dest.value.geti32()));
- Address sizeVal(uint32_t(size.value.geti32()));
+ Address destVal(uint32_t(dest.getSingleValue().geti32()));
+ Address sizeVal(uint32_t(size.getSingleValue().geti32()));
if ((uint64_t)destVal + sizeVal >
(uint64_t)instance.memorySize * Memory::kPageSize) {
trap("out of bounds memory access in memory.fill");
}
- uint8_t val(value.value.geti32());
+ uint8_t val(value.getSingleValue().geti32());
for (size_t i = 0; i < sizeVal; ++i) {
instance.externalInterface->store8(
instance.getFinalAddress(Literal(uint32_t(destVal + i)), 1), val);
@@ -2040,7 +2077,7 @@ private:
if (value.breaking()) {
return value;
}
- instance.multiValues.push_back(value.value);
+ instance.multiValues.push_back(value.getSingleValue());
return Flow();
}
Flow visitPop(Pop* curr) {
@@ -2092,7 +2129,7 @@ public:
RuntimeExpressionRunner(*this, scope, maxDepth).visit(function->body);
// cannot still be breaking, it means we missed our stop
assert(!flow.breaking() || flow.breakTo == RETURN_FLOW);
- Literal ret = flow.value;
+ Literal ret = flow.getSingleValue();
if (!Type::isSubType(ret.type, function->sig.results)) {
std::cerr << "calling " << function->name << " resulted in " << ret
<< " but the function type is " << function->sig.results
diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h
index e6b40eb08..420692808 100644
--- a/src/wasm-s-parser.h
+++ b/src/wasm-s-parser.h
@@ -235,13 +235,15 @@ private:
Expression* makeThrow(Element& s);
Expression* makeRethrow(Element& s);
Expression* makeBrOnExn(Element& s);
+ Expression* makeTupleMake(Element& s);
+ Expression* makeTupleExtract(Element& s);
// Helper functions
Type parseOptionalResultType(Element& s, Index& i);
Index parseMemoryLimits(Element& s, Index i);
std::vector<Type> parseParamOrLocal(Element& s);
std::vector<NameType> parseParamOrLocal(Element& s, size_t& localIndex);
- Type parseResults(Element& s);
+ std::vector<Type> parseResults(Element& s);
Signature parseTypeRef(Element& s);
size_t parseTypeUse(Element& s,
size_t startPos,
diff --git a/src/wasm-stack.h b/src/wasm-stack.h
index b42ae1253..8662e7835 100644
--- a/src/wasm-stack.h
+++ b/src/wasm-stack.h
@@ -143,7 +143,10 @@ public:
void visitDrop(Drop* curr);
void visitPush(Push* curr);
void visitPop(Pop* curr);
+ void visitTupleMake(TupleMake* curr);
+ void visitTupleExtract(TupleExtract* curr);
+ void emitResultType(Type type);
void emitIfElse(If* curr);
void emitCatch(Try* curr);
// emit an end at the end of a block/loop/if/try
@@ -168,6 +171,12 @@ private:
std::map<Type, size_t> numLocalsByType;
// (local index, tuple index) => binary local index
std::map<std::pair<Index, Index>, size_t> mappedLocals;
+
+ // Keeps track of the binary index of the scratch locals used to lower
+ // tuple.extract.
+ std::map<Type, Index> scratchLocals;
+ void countScratchLocals();
+ void setScratchLocals();
};
// Takes binaryen IR and converts it to something else (binary or stack IR)
@@ -227,6 +236,8 @@ public:
void visitDrop(Drop* curr);
void visitPush(Push* curr);
void visitPop(Pop* curr);
+ void visitTupleMake(TupleMake* curr);
+ void visitTupleExtract(TupleExtract* curr);
protected:
Function* func = nullptr;
@@ -793,6 +804,25 @@ template<typename SubType> void BinaryenIRWriter<SubType>::visitPop(Pop* curr) {
emit(curr);
}
+template<typename SubType>
+void BinaryenIRWriter<SubType>::visitTupleMake(TupleMake* curr) {
+ for (auto* operand : curr->operands) {
+ visit(operand);
+ }
+ // No need to handle unreachable since we don't actually emit an instruction
+ emit(curr);
+}
+
+template<typename SubType>
+void BinaryenIRWriter<SubType>::visitTupleExtract(TupleExtract* curr) {
+ visit(curr->tuple);
+ if (curr->type == Type::unreachable) {
+ emitUnreachable();
+ return;
+ }
+ emit(curr);
+}
+
// Binaryen IR to binary writer
class BinaryenIRToBinaryWriter
: public BinaryenIRWriter<BinaryenIRToBinaryWriter> {
diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h
index ee6d22aa5..4cf318db9 100644
--- a/src/wasm-traversal.h
+++ b/src/wasm-traversal.h
@@ -83,6 +83,8 @@ template<typename SubType, typename ReturnType = void> struct Visitor {
ReturnType visitUnreachable(Unreachable* curr) { return ReturnType(); }
ReturnType visitPush(Push* curr) { return ReturnType(); }
ReturnType visitPop(Pop* curr) { return ReturnType(); }
+ ReturnType visitTupleMake(TupleMake* curr) { return ReturnType(); }
+ ReturnType visitTupleExtract(TupleExtract* curr) { return ReturnType(); }
// Module-level visitors
ReturnType visitExport(Export* curr) { return ReturnType(); }
ReturnType visitGlobal(Global* curr) { return ReturnType(); }
@@ -192,6 +194,10 @@ template<typename SubType, typename ReturnType = void> struct Visitor {
DELEGATE(Push);
case Expression::Id::PopId:
DELEGATE(Pop);
+ case Expression::Id::TupleMakeId:
+ DELEGATE(TupleMake);
+ case Expression::Id::TupleExtractId:
+ DELEGATE(TupleExtract);
case Expression::Id::InvalidId:
default:
WASM_UNREACHABLE("unexpected expression type");
@@ -261,6 +267,8 @@ struct OverriddenVisitor {
UNIMPLEMENTED(Unreachable);
UNIMPLEMENTED(Push);
UNIMPLEMENTED(Pop);
+ UNIMPLEMENTED(TupleMake);
+ UNIMPLEMENTED(TupleExtract);
UNIMPLEMENTED(Export);
UNIMPLEMENTED(Global);
UNIMPLEMENTED(Function);
@@ -371,6 +379,10 @@ struct OverriddenVisitor {
DELEGATE(Push);
case Expression::Id::PopId:
DELEGATE(Pop);
+ case Expression::Id::TupleMakeId:
+ DELEGATE(TupleMake);
+ case Expression::Id::TupleExtractId:
+ DELEGATE(TupleExtract);
case Expression::Id::InvalidId:
default:
WASM_UNREACHABLE("unexpected expression type");
@@ -527,6 +539,12 @@ struct UnifiedExpressionVisitor : public Visitor<SubType, ReturnType> {
ReturnType visitPop(Pop* curr) {
return static_cast<SubType*>(this)->visitExpression(curr);
}
+ ReturnType visitTupleMake(TupleMake* curr) {
+ return static_cast<SubType*>(this)->visitExpression(curr);
+ }
+ ReturnType visitTupleExtract(TupleExtract* curr) {
+ return static_cast<SubType*>(this)->visitExpression(curr);
+ }
};
//
@@ -838,6 +856,12 @@ struct Walker : public VisitorType {
static void doVisitPop(SubType* self, Expression** currp) {
self->visitPop((*currp)->cast<Pop>());
}
+ static void doVisitTupleMake(SubType* self, Expression** currp) {
+ self->visitTupleMake((*currp)->cast<TupleMake>());
+ }
+ static void doVisitTupleExtract(SubType* self, Expression** currp) {
+ self->visitTupleExtract((*currp)->cast<TupleExtract>());
+ }
void setModule(Module* module) { currModule = module; }
@@ -1126,6 +1150,19 @@ struct PostWalker : public Walker<SubType, VisitorType> {
self->pushTask(SubType::doVisitPop, currp);
break;
}
+ case Expression::Id::TupleMakeId: {
+ self->pushTask(SubType::doVisitTupleMake, currp);
+ auto& operands = curr->cast<TupleMake>()->operands;
+ for (int i = int(operands.size()) - 1; i >= 0; --i) {
+ self->pushTask(SubType::scan, &operands[i]);
+ }
+ break;
+ }
+ case Expression::Id::TupleExtractId: {
+ self->pushTask(SubType::doVisitTupleExtract, currp);
+ self->pushTask(SubType::scan, &curr->cast<TupleExtract>()->tuple);
+ break;
+ }
case Expression::Id::NumExpressionIds:
WASM_UNREACHABLE("unexpected expression type");
}
diff --git a/src/wasm.h b/src/wasm.h
index 29844d855..507ffc047 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -538,6 +538,8 @@ public:
ThrowId,
RethrowId,
BrOnExnId,
+ TupleMakeId,
+ TupleExtractId,
NumExpressionIds
};
Id _id;
@@ -1149,6 +1151,25 @@ public:
void finalize();
};
+class TupleMake : public SpecificExpression<Expression::TupleMakeId> {
+public:
+ TupleMake(MixedArena& allocator) : operands(allocator) {}
+
+ ExpressionList operands;
+
+ void finalize();
+};
+
+class TupleExtract : public SpecificExpression<Expression::TupleExtractId> {
+public:
+ TupleExtract(MixedArena& allocator) {}
+
+ Expression* tuple;
+ Index index;
+
+ void finalize();
+};
+
// Globals
struct Importable {
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(); }
diff --git a/src/wasm2js.h b/src/wasm2js.h
index 0c09064e2..cdc109cfa 100644
--- a/src/wasm2js.h
+++ b/src/wasm2js.h
@@ -1894,6 +1894,14 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m,
unimplemented(curr);
WASM_UNREACHABLE("unimp");
}
+ Ref visitTupleMake(TupleMake* curr) {
+ unimplemented(curr);
+ WASM_UNREACHABLE("unimp");
+ }
+ Ref visitTupleExtract(TupleExtract* curr) {
+ unimplemented(curr);
+ WASM_UNREACHABLE("unimp");
+ }
private:
Ref makePointer(Expression* ptr, Address offset) {
diff --git a/test/binaryen.js/event.js b/test/binaryen.js/event.js
index 73af158f9..64d7659b8 100644
--- a/test/binaryen.js/event.js
+++ b/test/binaryen.js/event.js
@@ -7,7 +7,7 @@ function cleanInfo(info) {
}
var module = new binaryen.Module();
-module.setFeatures(binaryen.Features.ExceptionHandling);
+module.setFeatures(binaryen.Features.ExceptionHandling | binaryen.Features.Multivalue);
var pairType = binaryen.createType([binaryen.i32, binaryen.f32]);
diff --git a/test/multivalue.wast b/test/multivalue.wast
new file mode 100644
index 000000000..25e161ef4
--- /dev/null
+++ b/test/multivalue.wast
@@ -0,0 +1,144 @@
+(module
+ (import "env" "pair" (func $pair (result i32 i64)))
+
+ ;; Test basic lowering of tuple.make, tuple.extract, and tuple locals
+ (func $triple (result i32 i64 f32)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 7)
+ (f32.const 13)
+ )
+ )
+ (func $get-first (result i32)
+ (tuple.extract 0
+ (call $triple)
+ )
+ )
+ (func $get-second (result i64)
+ (tuple.extract 1
+ (call $triple)
+ )
+ )
+ (func $get-third (result f32)
+ (tuple.extract 2
+ (call $triple)
+ )
+ )
+ (func $reverse (result f32 i64 i32)
+ (local $x (i32 i64 f32))
+ (local.set $x
+ (call $triple)
+ )
+ (tuple.make
+ (tuple.extract 2
+ (local.get $x)
+ )
+ (tuple.extract 1
+ (local.get $x)
+ )
+ (tuple.extract 0
+ (local.get $x)
+ )
+ )
+ )
+
+ ;; Test lowering of multivalue drops
+ (func $drop-call
+ (drop
+ (call $pair)
+ )
+ )
+ (func $drop-tuple-make
+ (drop
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (func $drop-block
+ (drop
+ (block (result i32 i64)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ )
+
+ ;; Test multivalue control structures
+ (func $mv-return (result i32 i64)
+ (return
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (func $mv-return-in-block (result i32 i64)
+ (block (result i32 i64)
+ (return
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ )
+ (func $mv-block-break (result i32 i64)
+ (block $l (result i32 i64)
+ (br $l
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ )
+ (func $mv-block-br-if (result i32 i64)
+ (block $l (result i32 i64)
+ (br_if $l
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ (i32.const 1)
+ )
+ )
+ )
+ (func $mv-if (result i32 i64)
+ (if (result i32 i64)
+ (i32.const 1)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (func $mv-loop (result i32 i64)
+ (loop (result i32 i64)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (func $mv-switch (result i32 i64)
+ (block $a (result i32 i64)
+ (block $b (result i32 i64)
+ (br_table $a $b
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ (i32.const 0)
+ )
+ )
+ )
+ )
+) \ No newline at end of file
diff --git a/test/multivalue.wast.from-wast b/test/multivalue.wast.from-wast
new file mode 100644
index 000000000..57abbc21d
--- /dev/null
+++ b/test/multivalue.wast.from-wast
@@ -0,0 +1,145 @@
+(module
+ (type $none_=>_i32_i64 (func (result i32 i64)))
+ (type $none_=>_none (func))
+ (type $none_=>_f32_i64_i32 (func (result f32 i64 i32)))
+ (type $none_=>_i32 (func (result i32)))
+ (type $none_=>_i32_i64_f32 (func (result i32 i64 f32)))
+ (type $none_=>_i64 (func (result i64)))
+ (type $none_=>_f32 (func (result f32)))
+ (import "env" "pair" (func $pair (result i32 i64)))
+ (func $triple (; 1 ;) (result i32 i64 f32)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 7)
+ (f32.const 13)
+ )
+ )
+ (func $get-first (; 2 ;) (result i32)
+ (tuple.extract 0
+ (call $triple)
+ )
+ )
+ (func $get-second (; 3 ;) (result i64)
+ (tuple.extract 1
+ (call $triple)
+ )
+ )
+ (func $get-third (; 4 ;) (result f32)
+ (tuple.extract 2
+ (call $triple)
+ )
+ )
+ (func $reverse (; 5 ;) (result f32 i64 i32)
+ (local $x (i32 i64 f32))
+ (local.set $x
+ (call $triple)
+ )
+ (tuple.make
+ (tuple.extract 2
+ (local.get $x)
+ )
+ (tuple.extract 1
+ (local.get $x)
+ )
+ (tuple.extract 0
+ (local.get $x)
+ )
+ )
+ )
+ (func $drop-call (; 6 ;)
+ (drop
+ (call $pair)
+ )
+ )
+ (func $drop-tuple-make (; 7 ;)
+ (drop
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (func $drop-block (; 8 ;)
+ (drop
+ (block $block (result i32 i64)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ )
+ (func $mv-return (; 9 ;) (result i32 i64)
+ (return
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (func $mv-return-in-block (; 10 ;) (result i32 i64)
+ (block $block (result i32 i64)
+ (return
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ )
+ (func $mv-block-break (; 11 ;) (result i32 i64)
+ (block $l (result i32 i64)
+ (br $l
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ )
+ (func $mv-block-br-if (; 12 ;) (result i32 i64)
+ (block $l (result i32 i64)
+ (br_if $l
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ (i32.const 1)
+ )
+ )
+ )
+ (func $mv-if (; 13 ;) (result i32 i64)
+ (if (result i32 i64)
+ (i32.const 1)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (func $mv-loop (; 14 ;) (result i32 i64)
+ (loop $loop-in (result i32 i64)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (func $mv-switch (; 15 ;) (result i32 i64)
+ (block $a (result i32 i64)
+ (block $b (result i32 i64)
+ (br_table $a $b
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ (i32.const 0)
+ )
+ )
+ )
+ )
+)
diff --git a/test/multivalue.wast.fromBinary b/test/multivalue.wast.fromBinary
new file mode 100644
index 000000000..90d1331b1
--- /dev/null
+++ b/test/multivalue.wast.fromBinary
@@ -0,0 +1,439 @@
+(module
+ (type $none_=>_i32_i64 (func (result i32 i64)))
+ (type $none_=>_none (func))
+ (type $none_=>_f32_i64_i32 (func (result f32 i64 i32)))
+ (type $none_=>_i32 (func (result i32)))
+ (type $none_=>_i32_i64_f32 (func (result i32 i64 f32)))
+ (type $none_=>_i64 (func (result i64)))
+ (type $none_=>_f32 (func (result f32)))
+ (import "env" "pair" (func $pair (result i32 i64)))
+ (func $triple (; 1 ;) (result i32 i64 f32)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 7)
+ (f32.const 13)
+ )
+ )
+ (func $get-first (; 2 ;) (result i32)
+ (local $0 (i32 i64 f32))
+ (local $1 i64)
+ (local $2 i32)
+ (local.set $0
+ (call $triple)
+ )
+ (block (result i32)
+ (local.set $2
+ (tuple.extract 0
+ (local.get $0)
+ )
+ )
+ (drop
+ (block (result i64)
+ (local.set $1
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ (drop
+ (tuple.extract 2
+ (local.get $0)
+ )
+ )
+ (local.get $1)
+ )
+ )
+ (local.get $2)
+ )
+ )
+ (func $get-second (; 3 ;) (result i64)
+ (local $0 i64)
+ (local $1 (i32 i64 f32))
+ (local $2 i64)
+ (local $3 i32)
+ (local.set $1
+ (call $triple)
+ )
+ (drop
+ (block (result i32)
+ (local.set $3
+ (tuple.extract 0
+ (local.get $1)
+ )
+ )
+ (local.set $0
+ (block (result i64)
+ (local.set $2
+ (tuple.extract 1
+ (local.get $1)
+ )
+ )
+ (drop
+ (tuple.extract 2
+ (local.get $1)
+ )
+ )
+ (local.get $2)
+ )
+ )
+ (local.get $3)
+ )
+ )
+ (local.get $0)
+ )
+ (func $get-third (; 4 ;) (result f32)
+ (local $0 f32)
+ (local $1 (i32 i64 f32))
+ (local $2 i64)
+ (local $3 i32)
+ (local.set $1
+ (call $triple)
+ )
+ (drop
+ (block (result i32)
+ (local.set $3
+ (tuple.extract 0
+ (local.get $1)
+ )
+ )
+ (drop
+ (block (result i64)
+ (local.set $2
+ (tuple.extract 1
+ (local.get $1)
+ )
+ )
+ (local.set $0
+ (tuple.extract 2
+ (local.get $1)
+ )
+ )
+ (local.get $2)
+ )
+ )
+ (local.get $3)
+ )
+ )
+ (local.get $0)
+ )
+ (func $reverse (; 5 ;) (result f32 i64 i32)
+ (local $0 i32)
+ (local $1 i64)
+ (local $2 i64)
+ (local $3 f32)
+ (local $4 f32)
+ (local $5 (i32 i64 f32))
+ (local $6 i64)
+ (local $7 i32)
+ (local $8 i64)
+ (local $9 i32)
+ (local $10 i64)
+ (local $11 i32)
+ (local $12 i64)
+ (local $13 i32)
+ (local $14 f32)
+ (local.set $5
+ (call $triple)
+ )
+ (local.set $0
+ (block (result i32)
+ (local.set $7
+ (tuple.extract 0
+ (local.get $5)
+ )
+ )
+ (local.set $1
+ (block (result i64)
+ (local.set $6
+ (tuple.extract 1
+ (local.get $5)
+ )
+ )
+ (local.set $3
+ (tuple.extract 2
+ (local.get $5)
+ )
+ )
+ (local.get $6)
+ )
+ )
+ (local.get $7)
+ )
+ )
+ (drop
+ (block (result i32)
+ (local.set $9
+ (local.get $0)
+ )
+ (drop
+ (block (result i64)
+ (local.set $8
+ (local.get $1)
+ )
+ (local.set $4
+ (local.get $3)
+ )
+ (local.get $8)
+ )
+ )
+ (local.get $9)
+ )
+ )
+ (tuple.make
+ (block (result f32)
+ (local.set $14
+ (local.get $4)
+ )
+ (drop
+ (block (result i32)
+ (local.set $11
+ (local.get $0)
+ )
+ (local.set $2
+ (block (result i64)
+ (local.set $10
+ (local.get $1)
+ )
+ (drop
+ (local.get $3)
+ )
+ (local.get $10)
+ )
+ )
+ (local.get $11)
+ )
+ )
+ (local.get $14)
+ )
+ (local.get $2)
+ (block (result i32)
+ (local.set $13
+ (local.get $0)
+ )
+ (drop
+ (block (result i64)
+ (local.set $12
+ (local.get $1)
+ )
+ (drop
+ (local.get $3)
+ )
+ (local.get $12)
+ )
+ )
+ (local.get $13)
+ )
+ )
+ )
+ (func $drop-call (; 6 ;)
+ (local $0 (i32 i64))
+ (local $1 i32)
+ (local.set $0
+ (call $pair)
+ )
+ (drop
+ (block (result i32)
+ (local.set $1
+ (tuple.extract 0
+ (local.get $0)
+ )
+ )
+ (drop
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ (local.get $1)
+ )
+ )
+ )
+ (func $drop-tuple-make (; 7 ;)
+ (local $0 i32)
+ (drop
+ (block (result i32)
+ (local.set $0
+ (i32.const 42)
+ )
+ (drop
+ (i64.const 42)
+ )
+ (local.get $0)
+ )
+ )
+ )
+ (func $drop-block (; 8 ;)
+ (local $0 (i32 i64))
+ (local $1 i32)
+ (local.set $0
+ (block $label$1 (result i32 i64)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (drop
+ (block (result i32)
+ (local.set $1
+ (tuple.extract 0
+ (local.get $0)
+ )
+ )
+ (drop
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ (local.get $1)
+ )
+ )
+ )
+ (func $mv-return (; 9 ;) (result i32 i64)
+ (return
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (func $mv-return-in-block (; 10 ;) (result i32 i64)
+ (return
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (func $mv-block-break (; 11 ;) (result i32 i64)
+ (local $0 (i32 i64))
+ (local.set $0
+ (block $label$1 (result i32 i64)
+ (br $label$1
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ )
+ (tuple.make
+ (tuple.extract 0
+ (local.get $0)
+ )
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ )
+ (func $mv-block-br-if (; 12 ;) (result i32 i64)
+ (local $0 (i32 i64))
+ (local $1 (i32 i64))
+ (local.set $1
+ (block $label$1 (result i32 i64)
+ (local.set $0
+ (br_if $label$1
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ (i32.const 1)
+ )
+ )
+ (tuple.make
+ (tuple.extract 0
+ (local.get $0)
+ )
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ )
+ )
+ (tuple.make
+ (tuple.extract 0
+ (local.get $1)
+ )
+ (tuple.extract 1
+ (local.get $1)
+ )
+ )
+ )
+ (func $mv-if (; 13 ;) (result i32 i64)
+ (local $0 (i32 i64))
+ (local.set $0
+ (if (result i32 i64)
+ (i32.const 1)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (tuple.make
+ (tuple.extract 0
+ (local.get $0)
+ )
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ )
+ (func $mv-loop (; 14 ;) (result i32 i64)
+ (local $0 (i32 i64))
+ (local.set $0
+ (loop $label$1 (result i32 i64)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (tuple.make
+ (tuple.extract 0
+ (local.get $0)
+ )
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ )
+ (func $mv-switch (; 15 ;) (result i32 i64)
+ (local $0 (i32 i64))
+ (local $1 (i32 i64))
+ (local.set $1
+ (block $label$1 (result i32 i64)
+ (local.set $0
+ (block $label$2 (result i32 i64)
+ (br_table $label$1 $label$2
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ (i32.const 0)
+ )
+ )
+ )
+ (tuple.make
+ (tuple.extract 0
+ (local.get $0)
+ )
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ )
+ )
+ (tuple.make
+ (tuple.extract 0
+ (local.get $1)
+ )
+ (tuple.extract 1
+ (local.get $1)
+ )
+ )
+ )
+)
+
diff --git a/test/multivalue.wast.fromBinary.noDebugInfo b/test/multivalue.wast.fromBinary.noDebugInfo
new file mode 100644
index 000000000..ba4634b47
--- /dev/null
+++ b/test/multivalue.wast.fromBinary.noDebugInfo
@@ -0,0 +1,439 @@
+(module
+ (type $none_=>_i32_i64 (func (result i32 i64)))
+ (type $none_=>_none (func))
+ (type $none_=>_f32_i64_i32 (func (result f32 i64 i32)))
+ (type $none_=>_i32 (func (result i32)))
+ (type $none_=>_i32_i64_f32 (func (result i32 i64 f32)))
+ (type $none_=>_i64 (func (result i64)))
+ (type $none_=>_f32 (func (result f32)))
+ (import "env" "pair" (func $fimport$0 (result i32 i64)))
+ (func $0 (; 1 ;) (result i32 i64 f32)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 7)
+ (f32.const 13)
+ )
+ )
+ (func $1 (; 2 ;) (result i32)
+ (local $0 (i32 i64 f32))
+ (local $1 i64)
+ (local $2 i32)
+ (local.set $0
+ (call $0)
+ )
+ (block (result i32)
+ (local.set $2
+ (tuple.extract 0
+ (local.get $0)
+ )
+ )
+ (drop
+ (block (result i64)
+ (local.set $1
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ (drop
+ (tuple.extract 2
+ (local.get $0)
+ )
+ )
+ (local.get $1)
+ )
+ )
+ (local.get $2)
+ )
+ )
+ (func $2 (; 3 ;) (result i64)
+ (local $0 i64)
+ (local $1 (i32 i64 f32))
+ (local $2 i64)
+ (local $3 i32)
+ (local.set $1
+ (call $0)
+ )
+ (drop
+ (block (result i32)
+ (local.set $3
+ (tuple.extract 0
+ (local.get $1)
+ )
+ )
+ (local.set $0
+ (block (result i64)
+ (local.set $2
+ (tuple.extract 1
+ (local.get $1)
+ )
+ )
+ (drop
+ (tuple.extract 2
+ (local.get $1)
+ )
+ )
+ (local.get $2)
+ )
+ )
+ (local.get $3)
+ )
+ )
+ (local.get $0)
+ )
+ (func $3 (; 4 ;) (result f32)
+ (local $0 f32)
+ (local $1 (i32 i64 f32))
+ (local $2 i64)
+ (local $3 i32)
+ (local.set $1
+ (call $0)
+ )
+ (drop
+ (block (result i32)
+ (local.set $3
+ (tuple.extract 0
+ (local.get $1)
+ )
+ )
+ (drop
+ (block (result i64)
+ (local.set $2
+ (tuple.extract 1
+ (local.get $1)
+ )
+ )
+ (local.set $0
+ (tuple.extract 2
+ (local.get $1)
+ )
+ )
+ (local.get $2)
+ )
+ )
+ (local.get $3)
+ )
+ )
+ (local.get $0)
+ )
+ (func $4 (; 5 ;) (result f32 i64 i32)
+ (local $0 i32)
+ (local $1 i64)
+ (local $2 i64)
+ (local $3 f32)
+ (local $4 f32)
+ (local $5 (i32 i64 f32))
+ (local $6 i64)
+ (local $7 i32)
+ (local $8 i64)
+ (local $9 i32)
+ (local $10 i64)
+ (local $11 i32)
+ (local $12 i64)
+ (local $13 i32)
+ (local $14 f32)
+ (local.set $5
+ (call $0)
+ )
+ (local.set $0
+ (block (result i32)
+ (local.set $7
+ (tuple.extract 0
+ (local.get $5)
+ )
+ )
+ (local.set $1
+ (block (result i64)
+ (local.set $6
+ (tuple.extract 1
+ (local.get $5)
+ )
+ )
+ (local.set $3
+ (tuple.extract 2
+ (local.get $5)
+ )
+ )
+ (local.get $6)
+ )
+ )
+ (local.get $7)
+ )
+ )
+ (drop
+ (block (result i32)
+ (local.set $9
+ (local.get $0)
+ )
+ (drop
+ (block (result i64)
+ (local.set $8
+ (local.get $1)
+ )
+ (local.set $4
+ (local.get $3)
+ )
+ (local.get $8)
+ )
+ )
+ (local.get $9)
+ )
+ )
+ (tuple.make
+ (block (result f32)
+ (local.set $14
+ (local.get $4)
+ )
+ (drop
+ (block (result i32)
+ (local.set $11
+ (local.get $0)
+ )
+ (local.set $2
+ (block (result i64)
+ (local.set $10
+ (local.get $1)
+ )
+ (drop
+ (local.get $3)
+ )
+ (local.get $10)
+ )
+ )
+ (local.get $11)
+ )
+ )
+ (local.get $14)
+ )
+ (local.get $2)
+ (block (result i32)
+ (local.set $13
+ (local.get $0)
+ )
+ (drop
+ (block (result i64)
+ (local.set $12
+ (local.get $1)
+ )
+ (drop
+ (local.get $3)
+ )
+ (local.get $12)
+ )
+ )
+ (local.get $13)
+ )
+ )
+ )
+ (func $5 (; 6 ;)
+ (local $0 (i32 i64))
+ (local $1 i32)
+ (local.set $0
+ (call $fimport$0)
+ )
+ (drop
+ (block (result i32)
+ (local.set $1
+ (tuple.extract 0
+ (local.get $0)
+ )
+ )
+ (drop
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ (local.get $1)
+ )
+ )
+ )
+ (func $6 (; 7 ;)
+ (local $0 i32)
+ (drop
+ (block (result i32)
+ (local.set $0
+ (i32.const 42)
+ )
+ (drop
+ (i64.const 42)
+ )
+ (local.get $0)
+ )
+ )
+ )
+ (func $7 (; 8 ;)
+ (local $0 (i32 i64))
+ (local $1 i32)
+ (local.set $0
+ (block $label$1 (result i32 i64)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (drop
+ (block (result i32)
+ (local.set $1
+ (tuple.extract 0
+ (local.get $0)
+ )
+ )
+ (drop
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ (local.get $1)
+ )
+ )
+ )
+ (func $8 (; 9 ;) (result i32 i64)
+ (return
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (func $9 (; 10 ;) (result i32 i64)
+ (return
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (func $10 (; 11 ;) (result i32 i64)
+ (local $0 (i32 i64))
+ (local.set $0
+ (block $label$1 (result i32 i64)
+ (br $label$1
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ )
+ (tuple.make
+ (tuple.extract 0
+ (local.get $0)
+ )
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ )
+ (func $11 (; 12 ;) (result i32 i64)
+ (local $0 (i32 i64))
+ (local $1 (i32 i64))
+ (local.set $1
+ (block $label$1 (result i32 i64)
+ (local.set $0
+ (br_if $label$1
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ (i32.const 1)
+ )
+ )
+ (tuple.make
+ (tuple.extract 0
+ (local.get $0)
+ )
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ )
+ )
+ (tuple.make
+ (tuple.extract 0
+ (local.get $1)
+ )
+ (tuple.extract 1
+ (local.get $1)
+ )
+ )
+ )
+ (func $12 (; 13 ;) (result i32 i64)
+ (local $0 (i32 i64))
+ (local.set $0
+ (if (result i32 i64)
+ (i32.const 1)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (tuple.make
+ (tuple.extract 0
+ (local.get $0)
+ )
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ )
+ (func $13 (; 14 ;) (result i32 i64)
+ (local $0 (i32 i64))
+ (local.set $0
+ (loop $label$1 (result i32 i64)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ (tuple.make
+ (tuple.extract 0
+ (local.get $0)
+ )
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ )
+ (func $14 (; 15 ;) (result i32 i64)
+ (local $0 (i32 i64))
+ (local $1 (i32 i64))
+ (local.set $1
+ (block $label$1 (result i32 i64)
+ (local.set $0
+ (block $label$2 (result i32 i64)
+ (br_table $label$1 $label$2
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ (i32.const 0)
+ )
+ )
+ )
+ (tuple.make
+ (tuple.extract 0
+ (local.get $0)
+ )
+ (tuple.extract 1
+ (local.get $0)
+ )
+ )
+ )
+ )
+ (tuple.make
+ (tuple.extract 0
+ (local.get $1)
+ )
+ (tuple.extract 1
+ (local.get $1)
+ )
+ )
+ )
+)
+
diff --git a/test/spec/old_func.wast b/test/spec/old_func.wast
index 83fb1aff3..ba06d3a65 100644
--- a/test/spec/old_func.wast
+++ b/test/spec/old_func.wast
@@ -344,19 +344,6 @@
;; Invalid typing of result
(assert_invalid
- (module (func $type-multiple-result (result i32 i32) (unreachable)))
- "invalid result arity"
-)
-(assert_invalid
- (module
- (type (func (result i32 i32)))
- (func $type-multiple-result (type 0) (unreachable))
- )
- "invalid result arity"
-)
-
-
-(assert_invalid
(module (func $type-empty-i32 (result i32)))
"type mismatch"
)
@@ -529,4 +516,3 @@
))
"type mismatch"
)
-
diff --git a/test/unit/test_features.py b/test/unit/test_features.py
index db17c7f9d..4a79a6dd5 100644
--- a/test/unit/test_features.py
+++ b/test/unit/test_features.py
@@ -5,41 +5,45 @@ from . import utils
class FeatureValidationTest(utils.BinaryenTestCase):
- def check_feature(self, module, error, flag):
+ def check_feature(self, module, error, flag, const_flags=[]):
p = shared.run_process(shared.WASM_OPT +
- ['--mvp-features', '--print', '-o', os.devnull],
+ ['--mvp-features', '--print', '-o', os.devnull] +
+ const_flags,
input=module, check=False, capture_output=True)
self.assertIn(error, p.stderr)
self.assertIn('Fatal: error validating input', p.stderr)
self.assertNotEqual(p.returncode, 0)
p = shared.run_process(
shared.WASM_OPT + ['--mvp-features', '--print', '-o', os.devnull] +
- flag,
+ const_flags + [flag],
input=module,
check=False,
capture_output=True)
self.assertEqual(p.returncode, 0)
def check_simd(self, module, error):
- self.check_feature(module, error, ['--enable-simd'])
+ self.check_feature(module, error, '--enable-simd')
def check_sign_ext(self, module, error):
- self.check_feature(module, error, ['--enable-sign-ext'])
+ self.check_feature(module, error, '--enable-sign-ext')
def check_bulk_mem(self, module, error):
- self.check_feature(module, error, ['--enable-bulk-memory'])
+ self.check_feature(module, error, '--enable-bulk-memory')
def check_exception_handling(self, module, error):
# Exception handling implies reference types
- self.check_feature(module, error,
- ['--enable-reference-types',
- '--enable-exception-handling'])
+ self.check_feature(module, error, '--enable-exception-handling',
+ ['--enable-reference-types'])
def check_tail_call(self, module, error):
- self.check_feature(module, error, ['--enable-tail-call'])
+ self.check_feature(module, error, '--enable-tail-call')
def check_reference_types(self, module, error):
- self.check_feature(module, error, ['--enable-reference-types'])
+ self.check_feature(module, error, '--enable-reference-types')
+
+ def check_multivalue(self, module, error):
+ self.check_feature(module, error, '--enable-multivalue',
+ ['--enable-exception-handling'])
def test_v128_signature(self):
module = '''
@@ -195,6 +199,56 @@ class FeatureValidationTest(utils.BinaryenTestCase):
'''
self.check_exception_handling(module, 'Module has events')
+ def test_multivalue_import(self):
+ module = '''
+ (module
+ (import "env" "foo" (func $foo (result i32 i64)))
+ )
+ '''
+ self.check_multivalue(module, 'Imported multivalue function ' +
+ '(multivalue is not enabled)')
+
+ def test_multivalue_function(self):
+ module = '''
+ (module
+ (func $foo (result i32 i64)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ '''
+ self.check_multivalue(module, 'Multivalue function results ' +
+ '(multivalue is not enabled)')
+
+ def test_multivalue_event(self):
+ module = '''
+ (module
+ (event $foo (attr 0) (param i32 i64))
+ )
+ '''
+ self.check_multivalue(module, 'Multivalue event type ' +
+ '(multivalue is not enabled)')
+
+ def test_multivalue_block(self):
+ module = '''
+ (module
+ (func $foo
+ (drop
+ (block (result i32 i64)
+ (tuple.make
+ (i32.const 42)
+ (i64.const 42)
+ )
+ )
+ )
+ )
+ )
+ '''
+ self.check_multivalue(module, 'Multivalue block type ' +
+ '(multivalue is not enabled)')
+
class TargetFeaturesSectionTest(utils.BinaryenTestCase):
def test_atomics(self):