Skip to content

Commit 9e3dbb1

Browse files
authored
Initial multivalue support (#2675)
Implements parsing and emitting of tuple creation and extraction and tuple-typed control flow for both the text and binary formats. TODO: - Extend Precompute/interpreter to handle tuple values - C and JS API support/testing - Figure out how to lower in stack IR - Fuzzing
1 parent 3a275d0 commit 9e3dbb1

34 files changed

+1910
-194
lines changed

auto_update_tests.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -334,10 +334,10 @@ def update_spec_tests():
334334
('wasm-metadce', update_metadce_tests),
335335
('wasm-reduce', update_reduce_tests),
336336
('spec', update_spec_tests),
337-
('binaryenjs', update_binaryen_js_tests),
338337
('lld', lld.update_lld_tests),
339338
('wasm2js', wasm2js.update_wasm2js_tests),
340339
('binfmt', update_bin_fmt_tests),
340+
('binaryenjs', update_binaryen_js_tests),
341341
])
342342

343343

scripts/gen-s-parser.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,10 @@
480480
("try", "makeTry(s)"),
481481
("throw", "makeThrow(s)"),
482482
("rethrow", "makeRethrow(s)"),
483-
("br_on_exn", "makeBrOnExn(s)")
483+
("br_on_exn", "makeBrOnExn(s)"),
484+
# Multivalue pseudoinstructions
485+
("tuple.make", "makeTupleMake(s)"),
486+
("tuple.extract", "makeTupleExtract(s)")
484487
]
485488

486489

src/gen-s-parser.inc

+11
Original file line numberDiff line numberDiff line change
@@ -2562,6 +2562,17 @@ switch (op[0]) {
25622562
case 'r':
25632563
if (strcmp(op, "try") == 0) { return makeTry(s); }
25642564
goto parse_error;
2565+
case 'u': {
2566+
switch (op[6]) {
2567+
case 'e':
2568+
if (strcmp(op, "tuple.extract") == 0) { return makeTupleExtract(s); }
2569+
goto parse_error;
2570+
case 'm':
2571+
if (strcmp(op, "tuple.make") == 0) { return makeTupleMake(s); }
2572+
goto parse_error;
2573+
default: goto parse_error;
2574+
}
2575+
}
25652576
default: goto parse_error;
25662577
}
25672578
}

src/ir/ExpressionAnalyzer.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@ template<typename T> void visitImmediates(Expression* curr, T& visitor) {
233233
void visitUnreachable(Unreachable* curr) {}
234234
void visitPush(Push* curr) {}
235235
void visitPop(Pop* curr) {}
236+
void visitTupleMake(TupleMake* curr) {}
237+
void visitTupleExtract(TupleExtract* curr) {
238+
visitor.visitIndex(curr->index);
239+
}
236240
} singleton(curr, visitor);
237241
}
238242

src/ir/ExpressionManipulator.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,16 @@ flexibleCopy(Expression* original, Module& wasm, CustomCopier custom) {
261261
return builder.makePush(copy(curr->value));
262262
}
263263
Expression* visitPop(Pop* curr) { return builder.makePop(curr->type); }
264+
Expression* visitTupleMake(TupleMake* curr) {
265+
std::vector<Expression*> operands;
266+
for (auto* op : curr->operands) {
267+
operands.push_back(copy(op));
268+
}
269+
return builder.makeTupleMake(std::move(operands));
270+
}
271+
Expression* visitTupleExtract(TupleExtract* curr) {
272+
return builder.makeTupleExtract(copy(curr->tuple), curr->index);
273+
}
264274
};
265275

266276
Copier copier(wasm, custom);

src/ir/ReFinalize.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ void ReFinalize::visitNop(Nop* curr) { curr->finalize(); }
137137
void ReFinalize::visitUnreachable(Unreachable* curr) { curr->finalize(); }
138138
void ReFinalize::visitPush(Push* curr) { curr->finalize(); }
139139
void ReFinalize::visitPop(Pop* curr) { curr->finalize(); }
140+
void ReFinalize::visitTupleMake(TupleMake* curr) { curr->finalize(); }
141+
void ReFinalize::visitTupleExtract(TupleExtract* curr) { curr->finalize(); }
140142

141143
void ReFinalize::visitFunction(Function* curr) {
142144
// we may have changed the body from unreachable to none, which might be bad

src/ir/effects.h

+2
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,8 @@ struct EffectAnalyzer
452452
void visitUnreachable(Unreachable* curr) { branches = true; }
453453
void visitPush(Push* curr) { calls = true; }
454454
void visitPop(Pop* curr) { calls = true; }
455+
void visitTupleMake(TupleMake* curr) {}
456+
void visitTupleExtract(TupleExtract* curr) {}
455457

456458
// Helpers
457459

src/ir/module-utils.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ template<typename T> struct CallGraphPropertyAnalysis {
418418
}
419419
};
420420

421-
// Helper function for collecting the type signature used in a module
421+
// Helper function for collecting the type signatures used in a module
422422
//
423423
// Used when emitting or printing a module to give signatures canonical
424424
// indices. Signatures are sorted in order of decreasing frequency to minize the
@@ -441,6 +441,12 @@ collectSignatures(Module& wasm,
441441
TypeCounter(Counts& counts) : counts(counts) {}
442442

443443
void visitCallIndirect(CallIndirect* curr) { counts[curr->sig]++; }
444+
void visitBlock(Block* curr) {
445+
// TODO: Allow blocks to have input types as well
446+
if (curr->type.isMulti()) {
447+
counts[Signature(Type::none, curr->type)]++;
448+
}
449+
}
444450
};
445451
TypeCounter(counts).walk(func->body);
446452
};

src/ir/utils.h

+4
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ struct ReFinalize
157157
void visitUnreachable(Unreachable* curr);
158158
void visitPush(Push* curr);
159159
void visitPop(Pop* curr);
160+
void visitTupleMake(TupleMake* curr);
161+
void visitTupleExtract(TupleExtract* curr);
160162

161163
void visitFunction(Function* curr);
162164

@@ -224,6 +226,8 @@ struct ReFinalizeNode : public OverriddenVisitor<ReFinalizeNode> {
224226
void visitUnreachable(Unreachable* curr) { curr->finalize(); }
225227
void visitPush(Push* curr) { curr->finalize(); }
226228
void visitPop(Pop* curr) { curr->finalize(); }
229+
void visitTupleMake(TupleMake* curr) { curr->finalize(); }
230+
void visitTupleExtract(TupleExtract* curr) { curr->finalize(); }
227231

228232
void visitExport(Export* curr) { WASM_UNREACHABLE("unimp"); }
229233
void visitGlobal(Global* curr) { WASM_UNREACHABLE("unimp"); }

src/passes/DeadCodeElimination.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,10 @@ struct DeadCodeElimination
365365
DELEGATE(Rethrow);
366366
case Expression::Id::BrOnExnId:
367367
DELEGATE(BrOnExn);
368+
case Expression::Id::TupleMakeId:
369+
DELEGATE(TupleMake);
370+
case Expression::Id::TupleExtractId:
371+
DELEGATE(TupleExtract);
368372
case Expression::Id::InvalidId:
369373
WASM_UNREACHABLE("unimp");
370374
case Expression::Id::NumExpressionIds:

src/passes/Precompute.cpp

+26-19
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,11 @@ struct Precompute
188188
}
189189
// try to evaluate this into a const
190190
Flow flow = precomputeExpression(curr);
191-
if (flow.value.type.isVector()) {
191+
if (flow.values.size() > 1) {
192+
// TODO: handle multivalue types
193+
return;
194+
}
195+
if (flow.getSingleValue().type.isVector()) {
192196
return;
193197
}
194198
if (flow.breaking()) {
@@ -199,25 +203,26 @@ struct Precompute
199203
// this expression causes a return. if it's already a return, reuse the
200204
// node
201205
if (auto* ret = curr->dynCast<Return>()) {
202-
if (flow.value.type != Type::none) {
206+
if (flow.getSingleValue().type != Type::none) {
203207
// reuse a const value if there is one
204208
if (ret->value) {
205209
if (auto* value = ret->value->dynCast<Const>()) {
206-
value->value = flow.value;
210+
value->value = flow.getSingleValue();
207211
value->finalize();
208212
return;
209213
}
210214
}
211-
ret->value = Builder(*getModule()).makeConstExpression(flow.value);
215+
ret->value =
216+
Builder(*getModule()).makeConstExpression(flow.getSingleValue());
212217
} else {
213218
ret->value = nullptr;
214219
}
215220
} else {
216221
Builder builder(*getModule());
217-
replaceCurrent(
218-
builder.makeReturn(flow.value.type != Type::none
219-
? builder.makeConstExpression(flow.value)
220-
: nullptr));
222+
replaceCurrent(builder.makeReturn(
223+
flow.getSingleValue().type != Type::none
224+
? builder.makeConstExpression(flow.getSingleValue())
225+
: nullptr));
221226
}
222227
return;
223228
}
@@ -226,34 +231,36 @@ struct Precompute
226231
if (auto* br = curr->dynCast<Break>()) {
227232
br->name = flow.breakTo;
228233
br->condition = nullptr;
229-
if (flow.value.type != Type::none) {
234+
if (flow.getSingleValue().type != Type::none) {
230235
// reuse a const value if there is one
231236
if (br->value) {
232237
if (auto* value = br->value->dynCast<Const>()) {
233-
value->value = flow.value;
238+
value->value = flow.getSingleValue();
234239
value->finalize();
235240
br->finalize();
236241
return;
237242
}
238243
}
239-
br->value = Builder(*getModule()).makeConstExpression(flow.value);
244+
br->value =
245+
Builder(*getModule()).makeConstExpression(flow.getSingleValue());
240246
} else {
241247
br->value = nullptr;
242248
}
243249
br->finalize();
244250
} else {
245251
Builder builder(*getModule());
246-
replaceCurrent(
247-
builder.makeBreak(flow.breakTo,
248-
flow.value.type != Type::none
249-
? builder.makeConstExpression(flow.value)
250-
: nullptr));
252+
replaceCurrent(builder.makeBreak(
253+
flow.breakTo,
254+
flow.getSingleValue().type != Type::none
255+
? builder.makeConstExpression(flow.getSingleValue())
256+
: nullptr));
251257
}
252258
return;
253259
}
254260
// this was precomputed
255-
if (flow.value.type.isConcrete()) {
256-
replaceCurrent(Builder(*getModule()).makeConstExpression(flow.value));
261+
if (flow.getSingleValue().type.isConcrete()) {
262+
replaceCurrent(
263+
Builder(*getModule()).makeConstExpression(flow.getSingleValue()));
257264
worked = true;
258265
} else {
259266
ExpressionManipulator::nop(curr);
@@ -292,7 +299,7 @@ struct Precompute
292299
if (flow.breaking()) {
293300
return Literal();
294301
}
295-
return flow.value;
302+
return flow.getSingleValue();
296303
}
297304

298305
// Propagates values around. Returns whether we propagated.

src/passes/Print.cpp

+45-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,28 @@ static std::ostream& printLocal(Index index, Function* func, std::ostream& o) {
5757
return printName(name, o);
5858
}
5959

60+
// Unlike the default format, tuple types in s-expressions should not have
61+
// commas.
62+
struct LocalType {
63+
Type type;
64+
LocalType(Type type) : type(type){};
65+
};
66+
67+
static std::ostream& operator<<(std::ostream& o, const LocalType& localType) {
68+
Type type = localType.type;
69+
if (type.isMulti()) {
70+
const std::vector<Type>& types = type.expand();
71+
o << '(' << types[0];
72+
for (size_t i = 1; i < types.size(); ++i) {
73+
o << ' ' << types[i];
74+
}
75+
o << ')';
76+
return o;
77+
}
78+
o << type;
79+
return o;
80+
}
81+
6082
// Wrapper for printing signature names
6183
struct SigName {
6284
Signature sig;
@@ -1387,6 +1409,11 @@ struct PrintExpressionContents
13871409
o << ".pop";
13881410
restoreNormalColor(o);
13891411
}
1412+
void visitTupleMake(TupleMake* curr) { printMedium(o, "tuple.make"); }
1413+
void visitTupleExtract(TupleExtract* curr) {
1414+
printMedium(o, "tuple.extract ");
1415+
o << curr->index;
1416+
}
13901417
};
13911418

13921419
// Prints an expression in s-expr format, including both the
@@ -1955,6 +1982,22 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
19551982
PrintExpressionContents(currFunction, o).visit(curr);
19561983
o << ')';
19571984
}
1985+
void visitTupleMake(TupleMake* curr) {
1986+
o << '(';
1987+
PrintExpressionContents(currFunction, o).visit(curr);
1988+
incIndent();
1989+
for (auto operand : curr->operands) {
1990+
printFullLine(operand);
1991+
}
1992+
decIndent();
1993+
}
1994+
void visitTupleExtract(TupleExtract* curr) {
1995+
o << '(';
1996+
PrintExpressionContents(currFunction, o).visit(curr);
1997+
incIndent();
1998+
printFullLine(curr->tuple);
1999+
decIndent();
2000+
}
19582001
// Module-level visitors
19592002
void handleSignature(Signature curr, Name* funcName = nullptr) {
19602003
o << "(func";
@@ -2093,7 +2136,8 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
20932136
doIndent(o, indent);
20942137
o << '(';
20952138
printMinor(o, "local ");
2096-
printLocal(i, currFunction, o) << ' ' << curr->getLocalType(i) << ')';
2139+
printLocal(i, currFunction, o)
2140+
<< ' ' << LocalType(curr->getLocalType(i)) << ')';
20972141
o << maybeNewLine;
20982142
}
20992143
// Print the body.

src/support/small_vector.h

+5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ template<typename T, size_t N> class SmallVector {
4141
using value_type = T;
4242

4343
SmallVector() {}
44+
SmallVector(std::initializer_list<T> init) {
45+
for (T item : init) {
46+
push_back(item);
47+
}
48+
}
4449

4550
T& operator[](size_t i) {
4651
return const_cast<T&>(static_cast<const SmallVector<T, N>&>(*this)[i]);

src/wasm-binary.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -1179,8 +1179,8 @@ class WasmBinaryBuilder {
11791179

11801180
struct BreakTarget {
11811181
Name name;
1182-
int arity;
1183-
BreakTarget(Name name, int arity) : name(name), arity(arity) {}
1182+
Type type;
1183+
BreakTarget(Name name, Type type) : name(name), type(type) {}
11841184
};
11851185
std::vector<BreakTarget> breakStack;
11861186
// the names that breaks target. this lets us know if a block has breaks to it
@@ -1226,8 +1226,11 @@ class WasmBinaryBuilder {
12261226
void processExpressions();
12271227
void skipUnreachableCode();
12281228

1229+
void pushExpression(Expression* curr);
12291230
Expression* popExpression();
12301231
Expression* popNonVoidExpression();
1232+
Expression* popTuple(size_t numElems);
1233+
Expression* popTypedExpression(Type type);
12311234

12321235
void validateBinary(); // validations that cannot be performed on the Module
12331236
void processFunctions();

src/wasm-builder.h

+13
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,19 @@ class Builder {
601601
ret->finalize();
602602
return ret;
603603
}
604+
TupleMake* makeTupleMake(std::vector<Expression*>&& operands) {
605+
auto* ret = allocator.alloc<TupleMake>();
606+
ret->operands.set(operands);
607+
ret->finalize();
608+
return ret;
609+
}
610+
TupleExtract* makeTupleExtract(Expression* tuple, Index index) {
611+
auto* ret = allocator.alloc<TupleExtract>();
612+
ret->tuple = tuple;
613+
ret->index = index;
614+
ret->finalize();
615+
return ret;
616+
}
604617

605618
// Additional helpers
606619

0 commit comments

Comments
 (0)