#include "ir/branch-utils.h"
#include "ir/find_all.h"
#include "ir/local-graph.h"
#include "ir/parents.h"
#include "ir/properties.h"
#include "ir/type-updating.h"
#include "ir/utils.h"
#include "pass.h"
#include "support/unique_deferring_queue.h"
#include "wasm-builder.h"
#include "wasm.h"
namespace wasm {
namespace {
struct Heap2LocalOptimizer {
Function* func;
Module* module;
const PassOptions& passOptions;
LocalGraph localGraph;
Parents parents;
BranchUtils::BranchTargets branchTargets;
Heap2LocalOptimizer(Function* func,
Module* module,
const PassOptions& passOptions)
: func(func), module(module), passOptions(passOptions), localGraph(func),
parents(func->body), branchTargets(func->body) {
localGraph.computeSetInfluences();
FindAll<StructNew> allocations(func->body);
for (auto* allocation : allocations.list) {
if (!canHandleAsLocals(allocation->type)) {
continue;
}
convertToLocals(allocation);
}
}
bool canHandleAsLocals(Type type) {
if (type == Type::unreachable) {
return false;
}
auto& fields = type.getHeapType().getStruct().fields;
for (auto field : fields) {
if (!TypeUpdating::canHandleAsLocal(field.type)) {
return false;
}
if (field.isPacked()) {
return false;
}
}
return true;
}
struct Rewriter : PostWalker<Rewriter> {
StructNew* allocation;
Function* func;
Builder builder;
const FieldList& fields;
Rewriter(StructNew* allocation, Function* func, Module* module)
: allocation(allocation), func(func), builder(*module),
fields(allocation->type.getHeapType().getStruct().fields) {}
std::unordered_set<LocalSet*> sets;
std::unordered_set<Expression*> reached;
std::vector<Index> localIndexes;
void applyOptimization() {
for (auto field : fields) {
localIndexes.push_back(builder.addVar(func, field.type));
}
walk(func->body);
}
void adjustTypeFlowingThrough(Expression* curr) {
if (!reached.count(curr)) {
return;
}
assert(curr->type.isRef());
curr->type = Type(curr->type.getHeapType(), Nullable);
}
void visitBlock(Block* curr) { adjustTypeFlowingThrough(curr); }
void visitLoop(Loop* curr) { adjustTypeFlowingThrough(curr); }
void visitLocalSet(LocalSet* curr) {
if (!reached.count(curr)) {
return;
}
if (curr->isTee()) {
replaceCurrent(curr->value);
} else {
replaceCurrent(builder.makeDrop(curr->value));
}
}
void visitLocalGet(LocalGet* curr) {
if (!reached.count(curr)) {
return;
}
replaceCurrent(builder.makeRefNull(curr->type.getHeapType()));
}
void visitBreak(Break* curr) {
if (!reached.count(curr)) {
return;
}
curr->finalize();
}
void visitStructNew(StructNew* curr) {
if (curr != allocation) {
return;
}
std::vector<Expression*> contents;
if (!allocation->isWithDefault()) {
std::vector<Index> tempIndexes;
for (auto field : fields) {
tempIndexes.push_back(builder.addVar(func, field.type));
}
for (Index i = 0; i < tempIndexes.size(); i++) {
contents.push_back(
builder.makeLocalSet(tempIndexes[i], allocation->operands[i]));
}
for (Index i = 0; i < tempIndexes.size(); i++) {
contents.push_back(builder.makeLocalSet(
localIndexes[i],
builder.makeLocalGet(tempIndexes[i], fields[i].type)));
}
} else {
for (Index i = 0; i < localIndexes.size(); i++) {
contents.push_back(builder.makeLocalSet(
localIndexes[i],
builder.makeConstantExpression(Literal::makeZero(fields[i].type))));
}
}
contents.push_back(builder.makeRefNull(allocation->type.getHeapType()));
replaceCurrent(builder.makeBlock(contents));
}
void visitRefAs(RefAs* curr) {
if (!reached.count(curr)) {
return;
}
assert(curr->op == RefAsNonNull);
replaceCurrent(curr->value);
}
void visitStructSet(StructSet* curr) {
if (!reached.count(curr)) {
return;
}
replaceCurrent(builder.makeSequence(
builder.makeDrop(curr->ref),
builder.makeLocalSet(localIndexes[curr->index], curr->value)));
}
void visitStructGet(StructGet* curr) {
if (!reached.count(curr)) {
return;
}
replaceCurrent(
builder.makeSequence(builder.makeDrop(curr->ref),
builder.makeLocalGet(localIndexes[curr->index],
fields[curr->index].type)));
}
};
std::unordered_set<Expression*> seen;
enum class ParentChildInteraction {
Escapes,
FullyConsumes,
Flows,
Mixes,
};
void convertToLocals(StructNew* allocation) {
Rewriter rewriter(allocation, func, module);
using ChildAndParent = std::pair<Expression*, Expression*>;
UniqueNonrepeatingDeferredQueue<ChildAndParent> flows;
flows.push({allocation, parents.getParent(allocation)});
while (!flows.empty()) {
auto flow = flows.pop();
auto* child = flow.first;
auto* parent = flow.second;
if (!seen.emplace(parent).second) {
return;
}
switch (getParentChildInteraction(parent, child)) {
case ParentChildInteraction::Escapes: {
return;
}
case ParentChildInteraction::FullyConsumes: {
break;
}
case ParentChildInteraction::Flows: {
flows.push({parent, parents.getParent(parent)});
break;
}
case ParentChildInteraction::Mixes: {
return;
}
}
if (auto* set = parent->dynCast<LocalSet>()) {
rewriter.sets.insert(set);
if (auto* getsReached = getGetsReached(set)) {
for (auto* get : *getsReached) {
flows.push({get, parents.getParent(get)});
}
}
}
for (auto name : branchesSentByParent(child, parent)) {
flows.push({child, branchTargets.getTarget(name)});
}
rewriter.reached.insert(parent);
rewriter.reached.insert(child);
}
if (!getsAreExclusiveToSets(rewriter.sets)) {
return;
}
rewriter.applyOptimization();
}
ParentChildInteraction getParentChildInteraction(Expression* parent,
Expression* child) {
if (!parent) {
return ParentChildInteraction::Escapes;
}
struct Checker : public Visitor<Checker> {
Expression* child;
bool escapes = true;
bool fullyConsumes = false;
void visitBlock(Block* curr) {
escapes = false;
}
void visitLoop(Loop* curr) { escapes = false; }
void visitDrop(Drop* curr) {
escapes = false;
fullyConsumes = true;
}
void visitBreak(Break* curr) { escapes = false; }
void visitSwitch(Switch* curr) { escapes = false; }
void visitLocalGet(LocalGet* curr) { escapes = false; }
void visitLocalSet(LocalSet* curr) { escapes = false; }
void visitRefAs(RefAs* curr) {
if (curr->op == RefAsNonNull) {
escapes = false;
}
}
void visitStructSet(StructSet* curr) {
if (curr->ref == child) {
escapes = false;
fullyConsumes = true;
}
}
void visitStructGet(StructGet* curr) {
escapes = false;
fullyConsumes = true;
}
} checker;
checker.child = child;
checker.visit(parent);
if (checker.escapes) {
return ParentChildInteraction::Escapes;
}
if (checker.fullyConsumes || !parent->type.isRef()) {
return ParentChildInteraction::FullyConsumes;
}
if (Properties::getImmediateFallthrough(parent, passOptions, *module) ==
child) {
return ParentChildInteraction::Flows;
}
auto branches =
branchTargets.getBranches(BranchUtils::getDefinedName(parent));
if (branches.size() == 1 &&
BranchUtils::getSentValue(*branches.begin()) == child) {
if (auto* parentAsBlock = parent->dynCast<Block>()) {
if (parentAsBlock->list.back()->type == Type::unreachable) {
return ParentChildInteraction::Flows;
}
}
}
return ParentChildInteraction::Mixes;
}
LocalGraph::SetInfluences* getGetsReached(LocalSet* set) {
auto iter = localGraph.setInfluences.find(set);
if (iter != localGraph.setInfluences.end()) {
return &iter->second;
}
return nullptr;
}
BranchUtils::NameSet branchesSentByParent(Expression* child,
Expression* parent) {
BranchUtils::NameSet names;
BranchUtils::operateOnScopeNameUsesAndSentValues(
parent, [&](Name name, Expression* value) {
if (value == child) {
names.insert(name);
}
});
return names;
}
bool getsAreExclusiveToSets(const std::unordered_set<LocalSet*>& sets) {
std::unordered_set<LocalGet*> gets;
for (auto* set : sets) {
if (auto* getsReached = getGetsReached(set)) {
for (auto* get : *getsReached) {
gets.insert(get);
}
}
}
for (auto* get : gets) {
for (auto* set : localGraph.getSetses[get]) {
if (sets.count(set) == 0) {
return false;
}
}
}
return true;
}
};
struct Heap2Local : public WalkerPass<PostWalker<Heap2Local>> {
bool isFunctionParallel() override { return true; }
std::unique_ptr<Pass> create() override {
return std::make_unique<Heap2Local>();
}
void doWalkFunction(Function* func) {
Heap2LocalOptimizer(func, getModule(), getPassOptions());
}
};
}
Pass* createHeap2LocalPass() { return new Heap2Local(); }
}