Skip to content

Commit d895607

Browse files
authored
[IRBuilder] Use multiple scratch locals for inputs (#9336)
Instead of using a single tuple-typed scratch local for multivalue control flow inputs, allocate one scalar scratch local per input value. This avoids eagerly packaging scalar inputs into a tuple on scope entry. It also avoids allocating extra tuple scratch locals to extract elements when inputs are consumed inside the scope. Scalar input locals do have some downsides. Scopes that pass all of their inputs through untouched must now repackage them with tuple.make. Multivalue loop trampolines in fixLoopWithInput also still need a tuple scratch local to unpack branch values into the scalar input locals. However, real parameterized scopes usually consume their inputs as individual values. Scalar input locals can also be coalesced directly by standard local passes without tuple optimization, and they will allow individual parameters to be tunneled across nested scopes with different arities in a future commit.
1 parent 707afbd commit d895607

7 files changed

Lines changed: 378 additions & 203 deletions

File tree

‎src/passes/Outlining.cpp‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,6 @@
3434
#define ODBG(statement)
3535
#endif
3636

37-
// Check a Result or MaybeResult for error and call Fatal() if the error exists.
38-
#define ASSERT_OK(val) \
39-
if (auto _val = (val); auto err = _val.getErr()) { \
40-
Fatal() << err->msg; \
41-
}
42-
4337
namespace wasm {
4438

4539
// This custom hasher conforms to std::hash<Key>. Its purpose is to provide

‎src/support/result.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#ifndef wasm_support_result_h
1818
#define wasm_support_result_h
1919

20+
#include <cassert>
2021
#include <string>
2122
#include <variant>
2223

@@ -39,6 +40,13 @@ struct Err {
3940
return (typename decltype(_val)::ErrorType)(*err); \
4041
}
4142

43+
// Assert that a Result or MaybeResult is not an error.
44+
#define ASSERT_OK(val) \
45+
do { \
46+
[[maybe_unused]] auto _val = (val); \
47+
assert(!_val.getErr()); \
48+
} while (false)
49+
4250
// Represent a result of type T or an error message.
4351
template<typename T = Ok, typename E = Err> struct [[nodiscard]] Result {
4452
using ErrorType = E;

‎src/wasm-ir-builder.h‎

Lines changed: 8 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -433,10 +433,10 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {
433433
// function's `explicitLabelNames`.
434434
bool labelExplicit = false;
435435

436-
// If the control flow scope has an input type, we need to lower it using a
437-
// scratch local because we cannot represent control flow input in the IR.
436+
// If the control flow scope has an input type, we need to lower it using
437+
// scratch locals because we cannot represent control flow input in the IR.
438438
Type inputType;
439-
Index inputLocal = -1;
439+
std::vector<Index> inputLocals;
440440

441441
// If there are br_on_*, try_table, or resume branches that target this
442442
// scope and carry additional values, we need to use a scratch local to
@@ -461,13 +461,6 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {
461461
ScopeCtx() : scope(NoScope{}) {}
462462
ScopeCtx(Scope scope, Type inputType)
463463
: scope(scope), inputType(inputType) {}
464-
ScopeCtx(
465-
Scope scope, Name label, bool labelUsed, Type inputType, Index inputLocal)
466-
: scope(scope), label(label), labelUsed(labelUsed), inputType(inputType),
467-
inputLocal(inputLocal) {}
468-
ScopeCtx(Scope scope, Name label, bool labelUsed, Name branchLabel)
469-
: scope(scope), label(label), branchLabel(branchLabel),
470-
labelUsed(labelUsed) {}
471464

472465
static ScopeCtx makeFunc(Function* func) {
473466
return ScopeCtx(FuncScope{func}, Type::none);
@@ -514,7 +507,7 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {
514507
unreachable = false;
515508
if (!keepInput) {
516509
inputType = Type::none;
517-
inputLocal = -1;
510+
inputLocals.clear();
518511
}
519512
}
520513
bool isNone() { return std::get_if<NoScope>(&scope); }
@@ -715,35 +708,7 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {
715708
Index blockHint = 0;
716709
Index labelHint = 0;
717710

718-
Result<> pushScope(ScopeCtx&& scope) {
719-
if (auto label = scope.getOriginalLabel()) {
720-
// Assign a fresh label to the scope, if necessary.
721-
if (!scope.label) {
722-
scope.label = makeFresh(label);
723-
}
724-
// Record the original label to handle references to it correctly.
725-
labelDepths[label].push_back(scopeStack.size() + 1);
726-
}
727-
if (binaryPos) {
728-
scope.startPos = lastBinaryPos;
729-
lastBinaryPos = *binaryPos;
730-
}
731-
bool hasInput = scope.inputType != Type::none;
732-
Index inputLocal = scope.inputLocal;
733-
if (hasInput && !scope.isDelimiter()) {
734-
if (inputLocal == Index(-1)) {
735-
auto scratch = addScratchLocal(scope.inputType);
736-
CHECK_ERR(scratch);
737-
inputLocal = scope.inputLocal = *scratch;
738-
}
739-
CHECK_ERR(makeLocalSet(inputLocal));
740-
}
741-
scopeStack.emplace_back(std::move(scope));
742-
if (hasInput) {
743-
CHECK_ERR(makeLocalGet(inputLocal));
744-
}
745-
return Ok{};
746-
}
711+
Result<> pushScope(ScopeCtx&& scope);
747712

748713
ScopeCtx& getScope() {
749714
if (scopeStack.empty()) {
@@ -806,7 +771,9 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {
806771
Result<std::pair<Index, Name>> getExtraOutputLocalAndLabel(Index label,
807772
size_t extraArity);
808773
Expression* fixExtraOutput(ScopeCtx& scope, Name label, Expression* expr);
809-
void fixLoopWithInput(Loop* loop, Type inputType, Index scratch);
774+
void fixLoopWithInput(Loop* loop,
775+
Type inputType,
776+
const std::vector<Index>& scratch);
810777

811778
void applyAnnotations(Expression* expr, const CodeAnnotation& annotation);
812779

‎src/wasm/wasm-ir-builder.cpp‎

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,17 @@ struct IRBuilder::ChildPopper
778778
ConstraintCollector{builder, children}.visitStackSwitch(curr, ct);
779779
return popConstrainedChildren(children);
780780
}
781+
782+
Result<std::vector<Expression*>> popInputs(Type inputType) {
783+
std::vector<Expression*> inputs(inputType.size());
784+
std::vector<Child> children;
785+
children.reserve(inputType.size());
786+
for (size_t i = 0; i < inputType.size(); ++i) {
787+
children.push_back({&inputs[i], {inputType[i]}});
788+
}
789+
CHECK_ERR(popConstrainedChildren(children));
790+
return inputs;
791+
}
781792
};
782793

783794
Result<> IRBuilder::visit(Expression* curr) {
@@ -877,6 +888,42 @@ IRBuilder::visitTryTableStart(TryTable* trytable, Name label, Type inputType) {
877888
return pushScope(ScopeCtx::makeTryTable(trytable, label, inputType));
878889
}
879890

891+
Result<> IRBuilder::pushScope(ScopeCtx&& scope) {
892+
if (auto label = scope.getOriginalLabel()) {
893+
// Assign a fresh label to the scope, if necessary.
894+
if (!scope.label) {
895+
scope.label = makeFresh(label);
896+
}
897+
// Record the original label to handle references to it correctly.
898+
labelDepths[label].push_back(scopeStack.size() + 1);
899+
}
900+
if (binaryPos) {
901+
scope.startPos = lastBinaryPos;
902+
lastBinaryPos = *binaryPos;
903+
}
904+
bool hasInput = scope.inputType != Type::none;
905+
if (hasInput && !scope.isDelimiter()) {
906+
auto inputs = ChildPopper{*this}.popInputs(scope.inputType);
907+
CHECK_ERR(inputs);
908+
scope.inputLocals.reserve(inputs->size());
909+
for (size_t i = 0; i < inputs->size(); ++i) {
910+
auto scratch = addScratchLocal(scope.inputType[i]);
911+
CHECK_ERR(scratch);
912+
scope.inputLocals.push_back(*scratch);
913+
pushSynthetic(builder.makeLocalSet(*scratch, (*inputs)[i]));
914+
}
915+
}
916+
scopeStack.emplace_back(std::move(scope));
917+
if (hasInput) {
918+
auto& newScope = getScope();
919+
for (size_t i = 0; i < newScope.inputLocals.size(); ++i) {
920+
pushSynthetic(
921+
builder.makeLocalGet(newScope.inputLocals[i], newScope.inputType[i]));
922+
}
923+
}
924+
return Ok{};
925+
}
926+
880927
Result<Expression*> IRBuilder::finishScope(Block* block) {
881928
#if IR_BUILDER_DEBUG
882929
if (auto* loc = std::get_if<Function::DebugLocation>(&debugLoc)) {
@@ -1212,8 +1259,8 @@ Result<> IRBuilder::visitEnd() {
12121259
if (scope.inputType != Type::none && scope.labelUsed) {
12131260
// Branches to this loop carry values, but Binaryen IR does not support
12141261
// that. Fix this by trampolining the branches through new code that sets
1215-
// the branch value to the appropriate scratch local.
1216-
fixLoopWithInput(loop, scope.inputType, scope.inputLocal);
1262+
// the branch value to the appropriate scratch locals.
1263+
fixLoopWithInput(loop, scope.inputType, scope.inputLocals);
12171264
}
12181265
loop->finalize(loop->type);
12191266
push(loop);
@@ -1222,8 +1269,19 @@ Result<> IRBuilder::visitEnd() {
12221269
if (scope.inputType != Type::none) {
12231270
// Normally an if without an else must have type none, but if there is an
12241271
// input parameter, the empty else arm must propagate its value.
1225-
// Synthesize an else arm that loads the value from the scratch local.
1226-
iff->ifFalse = builder.makeLocalGet(scope.inputLocal, scope.inputType);
1272+
// Synthesize an else arm that loads the value from the scratch locals.
1273+
if (scope.inputType.isSingle()) {
1274+
iff->ifFalse =
1275+
builder.makeLocalGet(scope.inputLocals[0], scope.inputType);
1276+
} else {
1277+
std::vector<Expression*> gets;
1278+
gets.reserve(scope.inputLocals.size());
1279+
for (size_t i = 0; i < scope.inputLocals.size(); ++i) {
1280+
gets.push_back(
1281+
builder.makeLocalGet(scope.inputLocals[i], scope.inputType[i]));
1282+
}
1283+
iff->ifFalse = builder.makeTupleMake(gets);
1284+
}
12271285
} else {
12281286
iff->ifFalse = nullptr;
12291287
}
@@ -1368,17 +1426,18 @@ IRBuilder::fixExtraOutput(ScopeCtx& scope, Name label, Expression* curr) {
13681426
return curr;
13691427
}
13701428

1371-
// Branches to this loop need to be trampolined through code that sets the value
1372-
// carried by the branch to the appropriate scratch local before branching to
1373-
// the loop. Transform this:
1429+
// Branches to this loop need to be trampolined through code that sets the
1430+
// values carried by the branch to the appropriate scratch locals before
1431+
// branching to the loop. Transform this:
13741432
//
13751433
// (loop $l (param t1) (result t2) ...)
13761434
//
1377-
// to this:
1435+
// to this (or, when t1 is a tuple, using a tuple scratch local to extract each
1436+
// element into its corresponding input scratch local):
13781437
//
13791438
// (loop $l0 (result t2)
13801439
// (block $l1 (result t2)
1381-
// (local.set $scratch ;; set the branch values to the scratch local
1440+
// (local.set $scratch ;; set the branch value to the scratch local
13821441
// (block $l (result t1)
13831442
// (br $l1 ;; exit the loop with the fallthrough value, if any.
13841443
// ... ;; contains branches to $l
@@ -1388,7 +1447,9 @@ IRBuilder::fixExtraOutput(ScopeCtx& scope, Name label, Expression* curr) {
13881447
// (br $l0) ;; continue the loop
13891448
// )
13901449
// )
1391-
void IRBuilder::fixLoopWithInput(Loop* loop, Type inputType, Index scratch) {
1450+
void IRBuilder::fixLoopWithInput(Loop* loop,
1451+
Type inputType,
1452+
const std::vector<Index>& scratch) {
13921453
auto l = loop->name;
13931454
auto l0 = makeFresh(l, 0);
13941455
auto l1 = makeFresh(l, 1);
@@ -1399,10 +1460,22 @@ void IRBuilder::fixLoopWithInput(Loop* loop, Type inputType, Index scratch) {
13991460
loop->body, l, builder.makeBreak(l1), inputType)
14001461
: builder.makeBlock(l, {builder.makeBreak(l1, loop->body)}, inputType);
14011462

1402-
Block* outer = builder.makeBlock(
1403-
l1,
1404-
{builder.makeLocalSet(scratch, inner), builder.makeBreak(l0)},
1405-
loop->type);
1463+
std::vector<Expression*> list;
1464+
if (inputType.isSingle()) {
1465+
list.push_back(builder.makeLocalSet(scratch[0], inner));
1466+
} else {
1467+
auto tupleScratch = addScratchLocal(inputType);
1468+
ASSERT_OK(tupleScratch);
1469+
list.push_back(builder.makeLocalSet(*tupleScratch, inner));
1470+
for (Index i = 0; i < scratch.size(); ++i) {
1471+
list.push_back(builder.makeLocalSet(
1472+
scratch[i],
1473+
builder.makeTupleExtract(builder.makeLocalGet(*tupleScratch, inputType),
1474+
i)));
1475+
}
1476+
}
1477+
list.push_back(builder.makeBreak(l0));
1478+
Block* outer = builder.makeBlock(l1, list, loop->type);
14061479

14071480
loop->body = outer;
14081481
loop->name = l0;

0 commit comments

Comments
 (0)