blob: 6003f944d5721e85981081194dc1561306938396 [file] [log] [blame] [edit]
/*
* Copyright 2023 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef wasm_passes_stringify_walker_h
#define wasm_passes_stringify_walker_h
#include <queue>
#include "ir/iteration.h"
#include "ir/module-utils.h"
#include "wasm-traversal.h"
namespace wasm {
/*
* This walker does a normal postorder traversal except that it defers
* traversing the contents of control flow structures it encounters. This
* effectively un-nests control flow.
*
* For example, the below (contrived) wat:
* 1 : (block
* 2 : (if
* 3 : (i32.const 0)
* 4 : (then (return (i32.const 1)))
* 5 : (else (return (i32.const 0)))))
* 6 : (drop
* 7 : (i32.add
* 8 : (i32.const 20)
* 9 : (i32.const 10)))
* 10: (if
* 11: (i32.const 1)
* 12: (then (return (i32.const 2)))
* 14: )
* Would have its expressions visited in the following order (based on line
* number):
* 1, 3, 2, 8, 9, 7, 6, 11, 10, 4, 5, 12
*
* Of note:
* - The visits to if-True on line 4 and if-False on line 5 are deferred until
* after the rest of the siblings of the if expression on line 2 are
* visited.
* - The if-condition (i32.const 0) on line 3 is visited before the if
* expression on line 2. Similarly, the if-condition (i32.const 1) on line
* 11 is visited before the if expression on line 10.
* - The add (line 7) binary operator's left and right children (lines 8 - 9)
* are visited first as they need to be on the stack before the add
* operation is executed.
*/
template<typename SubType>
struct StringifyWalker
: public PostWalker<SubType, UnifiedExpressionVisitor<SubType>> {
using Super = PostWalker<SubType, UnifiedExpressionVisitor<SubType>>;
struct SeparatorReason {
struct FuncStart {
Function* func;
};
struct BlockStart {
Block* block;
};
struct IfStart {
If* iff;
};
struct ElseStart {};
struct LoopStart {
Loop* loop;
};
struct TryStart {
Try* tryy;
};
struct CatchStart {
Name tag;
};
struct CatchAllStart {};
struct TryTableStart {
TryTable* tryt;
};
struct End {
Expression* curr;
};
using Separator = std::variant<FuncStart,
BlockStart,
IfStart,
ElseStart,
LoopStart,
TryStart,
CatchStart,
CatchAllStart,
TryTableStart,
End>;
Separator reason;
SeparatorReason(Separator reason) : reason(reason) {}
static SeparatorReason makeFuncStart(Function* func) {
return SeparatorReason(FuncStart{func});
}
static SeparatorReason makeBlockStart(Block* block) {
return SeparatorReason(BlockStart{block});
}
static SeparatorReason makeIfStart(If* iff) {
return SeparatorReason(IfStart{iff});
}
static SeparatorReason makeElseStart() {
return SeparatorReason(ElseStart{});
}
static SeparatorReason makeLoopStart(Loop* loop) {
return SeparatorReason(LoopStart{loop});
}
static SeparatorReason makeTryStart(Try* tryy) {
return SeparatorReason(TryStart{tryy});
}
static SeparatorReason makeCatchStart(Name tag) {
return SeparatorReason(CatchStart{tag});
}
static SeparatorReason makeCatchAllStart() {
return SeparatorReason(CatchAllStart{});
}
static SeparatorReason makeTryTableStart(TryTable* tryt) {
return SeparatorReason(TryTableStart{tryt});
}
static SeparatorReason makeEnd() { return SeparatorReason(End{}); }
FuncStart* getFuncStart() { return std::get_if<FuncStart>(&reason); }
BlockStart* getBlockStart() { return std::get_if<BlockStart>(&reason); }
IfStart* getIfStart() { return std::get_if<IfStart>(&reason); }
ElseStart* getElseStart() { return std::get_if<ElseStart>(&reason); }
LoopStart* getLoopStart() { return std::get_if<LoopStart>(&reason); }
TryStart* getTryStart() { return std::get_if<TryStart>(&reason); }
CatchStart* getCatchStart() { return std::get_if<CatchStart>(&reason); }
CatchAllStart* getCatchAllStart() {
return std::get_if<CatchAllStart>(&reason);
}
TryTableStart* getTryTableStart() {
return std::get_if<TryTableStart>(&reason);
}
End* getEnd() { return std::get_if<End>(&reason); }
};
friend std::ostream&
operator<<(std::ostream& o,
typename StringifyWalker::SeparatorReason reason) {
if (reason.getFuncStart()) {
return o << "Func Start";
} else if (reason.getBlockStart()) {
return o << "Block Start";
} else if (reason.getIfStart()) {
return o << "If Start";
} else if (reason.getElseStart()) {
return o << "Else Start";
} else if (reason.getLoopStart()) {
return o << "Loop Start";
} else if (reason.getTryStart()) {
return o << "Try Start";
} else if (reason.getCatchStart()) {
return o << "Catch Start";
} else if (reason.getCatchAllStart()) {
return o << "Catch All Start";
} else if (reason.getTryTableStart()) {
return o << "Try Table Start";
} else if (reason.getEnd()) {
return o << "End";
}
return o << "~~~Undefined in operator<< overload~~~";
}
// To ensure control flow children are walked consistently during outlining,
// we push a copy of the control flow expression. This avoids an issue where
// control flow no longer points to the same expression after being
// outlined into a new function.
std::queue<Expression*> controlFlowQueue;
/*
* To initiate the walk, subclasses should call walkModule with a pointer to
* the wasm module.
*
* Member functions addUniqueSymbol and visitExpression are provided as
* extension points for subclasses. These functions will be called at
* appropriate points during the walk and should be implemented by subclasses.
*/
void visitExpression(Expression* curr);
void addUniqueSymbol(SeparatorReason reason);
void doWalkModule(Module* module);
void doWalkFunction(Function* func);
static void scan(SubType* self, Expression** currp);
static void doVisitExpression(SubType* self, Expression** currp);
void flushControlFlowQueue() {
while (!controlFlowQueue.empty()) {
dequeueControlFlow();
}
}
void dequeueControlFlow();
};
// Implementation follows
// This walker supplies its own doWalkModule because it does not make sense to
// walk anything besides defined functions.
template<typename SubType>
inline void StringifyWalker<SubType>::doWalkModule(Module* module) {
ModuleUtils::iterDefinedFunctions(
*module, [&](Function* func) { this->walkFunction(func); });
}
template<typename SubType>
inline void StringifyWalker<SubType>::doWalkFunction(Function* func) {
addUniqueSymbol(SeparatorReason::makeFuncStart(func));
Super::walk(func->body);
addUniqueSymbol(SeparatorReason::makeEnd());
flushControlFlowQueue();
}
template<typename SubType>
inline void StringifyWalker<SubType>::scan(SubType* self, Expression** currp) {
Expression* curr = *currp;
if (Properties::isControlFlowStructure(curr)) {
self->pushTask(doVisitExpression, currp);
// The if-condition is a value child consumed by the if control flow, which
// makes the if-condition a true sibling rather than part of its contents in
// the binary format
for (auto*& child : ValueChildIterator(curr)) {
scan(self, &child);
}
self->controlFlowQueue.push(curr);
} else {
Super::scan(self, currp);
}
}
// This dequeueControlFlow is responsible for visiting the children expressions
// of control flow.
template<typename SubType> void StringifyWalker<SubType>::dequeueControlFlow() {
auto& queue = controlFlowQueue;
Expression* curr = queue.front();
queue.pop();
// TODO: Issue #5796, Make a ControlChildIterator
switch (curr->_id) {
case Expression::Id::BlockId: {
auto* block = curr->cast<Block>();
addUniqueSymbol(SeparatorReason::makeBlockStart(block));
for (auto& child : block->list) {
Super::walk(child);
}
addUniqueSymbol(SeparatorReason::makeEnd());
break;
}
case Expression::Id::IfId: {
auto* iff = curr->cast<If>();
addUniqueSymbol(SeparatorReason::makeIfStart(iff));
Super::walk(iff->ifTrue);
if (iff->ifFalse) {
addUniqueSymbol(SeparatorReason::makeElseStart());
Super::walk(iff->ifFalse);
}
addUniqueSymbol(SeparatorReason::makeEnd());
break;
}
case Expression::Id::TryId: {
auto* tryy = curr->cast<Try>();
assert(!tryy->isDelegate() && "TODO: try-delegate");
addUniqueSymbol(SeparatorReason::makeTryStart(tryy));
Super::walk(tryy->body);
for (size_t i = 0; i < tryy->catchBodies.size(); i++) {
if (tryy->hasCatchAll() && i == tryy->catchBodies.size() - 1) {
addUniqueSymbol(SeparatorReason::makeCatchAllStart());
} else {
addUniqueSymbol(SeparatorReason::makeCatchStart(tryy->catchTags[i]));
}
Super::walk(tryy->catchBodies[i]);
}
addUniqueSymbol(SeparatorReason::makeEnd());
break;
}
case Expression::Id::LoopId: {
auto* loop = curr->cast<Loop>();
addUniqueSymbol(SeparatorReason::makeLoopStart(loop));
Super::walk(loop->body);
addUniqueSymbol(SeparatorReason::makeEnd());
break;
}
case Expression::Id::TryTableId: {
auto* tryt = curr->cast<TryTable>();
addUniqueSymbol(SeparatorReason::makeTryTableStart(tryt));
Super::walk(tryt->body);
addUniqueSymbol(SeparatorReason::makeEnd());
break;
}
default: {
assert(Properties::isControlFlowStructure(curr));
WASM_UNREACHABLE("unexpected expression");
}
}
}
template<typename SubType>
void StringifyWalker<SubType>::doVisitExpression(SubType* self,
Expression** currp) {
Expression* curr = *currp;
self->visit(curr);
}
template<typename SubType>
inline void StringifyWalker<SubType>::addUniqueSymbol(SeparatorReason reason) {
// TODO: Add the following static_assert when the compilers running our GitHub
// actions are updated enough to know that this is a constant condition:
// static_assert(&StringifyWalker<SubType>::addUniqueSymbol !=
// &SubType::addUniqueSymbol);
auto self = static_cast<SubType*>(this);
self->addUniqueSymbol(reason);
}
} // namespace wasm
#endif // wasm_passes_stringify_walker_h