#ifndef wasm_ir_effects_h
#define wasm_ir_effects_h
#include "pass.h"
#include "wasm-traversal.h"
namespace wasm {
struct EffectAnalyzer
: public PostWalker<EffectAnalyzer, OverriddenVisitor<EffectAnalyzer>> {
EffectAnalyzer(const PassOptions& passOptions,
FeatureSet features,
Expression* ast = nullptr)
: ignoreImplicitTraps(passOptions.ignoreImplicitTraps),
debugInfo(passOptions.debugInfo), features(features) {
if (ast) {
analyze(ast);
}
}
bool ignoreImplicitTraps;
bool debugInfo;
FeatureSet features;
void analyze(Expression* ast) {
breakTargets.clear();
walk(ast);
assert(tryDepth == 0);
}
bool branchesOut = false;
bool calls = false;
std::set<Index> localsRead;
std::set<Index> localsWritten;
std::set<Name> globalsRead;
std::set<Name> globalsWritten;
bool readsMemory = false;
bool writesMemory = false;
bool implicitTrap = false;
bool isAtomic = false;
bool throws = false;
size_t tryDepth = 0;
size_t catchDepth = 0;
bool danglingPop = false;
static void scan(EffectAnalyzer* self, Expression** currp) {
Expression* curr = *currp;
if (curr->is<Try>()) {
self->pushTask(doVisitTry, currp);
self->pushTask(doEndCatch, currp);
self->pushTask(scan, &curr->cast<Try>()->catchBody);
self->pushTask(doStartCatch, currp);
self->pushTask(scan, &curr->cast<Try>()->body);
self->pushTask(doStartTry, currp);
return;
}
PostWalker<EffectAnalyzer, OverriddenVisitor<EffectAnalyzer>>::scan(self,
currp);
}
static void doStartTry(EffectAnalyzer* self, Expression** currp) {
self->tryDepth++;
}
static void doStartCatch(EffectAnalyzer* self, Expression** currp) {
assert(self->tryDepth > 0 && "try depth cannot be negative");
self->tryDepth--;
self->catchDepth++;
}
static void doEndCatch(EffectAnalyzer* self, Expression** currp) {
assert(self->catchDepth > 0 && "catch depth cannot be negative");
self->catchDepth--;
}
bool accessesLocal() const {
return localsRead.size() + localsWritten.size() > 0;
}
bool accessesGlobal() const {
return globalsRead.size() + globalsWritten.size() > 0;
}
bool accessesMemory() const { return calls || readsMemory || writesMemory; }
bool transfersControlFlow() const {
return branchesOut || throws || hasExternalBreakTargets();
}
bool hasGlobalSideEffects() const {
return calls || globalsWritten.size() > 0 || writesMemory || isAtomic ||
throws;
}
bool hasSideEffects() const {
return hasGlobalSideEffects() || localsWritten.size() > 0 ||
transfersControlFlow() || implicitTrap || danglingPop;
}
bool hasAnything() const {
return hasSideEffects() || accessesLocal() || readsMemory ||
accessesGlobal() || isAtomic;
}
bool noticesGlobalSideEffects() const {
return calls || readsMemory || isAtomic || globalsRead.size();
}
bool hasExternalBreakTargets() const { return !breakTargets.empty(); }
bool invalidates(const EffectAnalyzer& other) {
if ((transfersControlFlow() && other.hasSideEffects()) ||
(other.transfersControlFlow() && hasSideEffects()) ||
((writesMemory || calls) && other.accessesMemory()) ||
(accessesMemory() && (other.writesMemory || other.calls)) ||
(danglingPop || other.danglingPop)) {
return true;
}
if ((isAtomic && other.accessesMemory()) ||
(other.isAtomic && accessesMemory())) {
return true;
}
for (auto local : localsWritten) {
if (other.localsWritten.count(local) || other.localsRead.count(local)) {
return true;
}
}
for (auto local : localsRead) {
if (other.localsWritten.count(local)) {
return true;
}
}
if ((accessesGlobal() && other.calls) ||
(other.accessesGlobal() && calls)) {
return true;
}
for (auto global : globalsWritten) {
if (other.globalsWritten.count(global) ||
other.globalsRead.count(global)) {
return true;
}
}
for (auto global : globalsRead) {
if (other.globalsWritten.count(global)) {
return true;
}
}
if ((implicitTrap && other.transfersControlFlow()) ||
(other.implicitTrap && transfersControlFlow())) {
return true;
}
if ((implicitTrap && other.hasGlobalSideEffects()) ||
(other.implicitTrap && hasGlobalSideEffects())) {
return true;
}
return false;
}
void mergeIn(EffectAnalyzer& other) {
branchesOut = branchesOut || other.branchesOut;
calls = calls || other.calls;
readsMemory = readsMemory || other.readsMemory;
writesMemory = writesMemory || other.writesMemory;
implicitTrap = implicitTrap || other.implicitTrap;
isAtomic = isAtomic || other.isAtomic;
throws = throws || other.throws;
danglingPop = danglingPop || other.danglingPop;
for (auto i : other.localsRead) {
localsRead.insert(i);
}
for (auto i : other.localsWritten) {
localsWritten.insert(i);
}
for (auto i : other.globalsRead) {
globalsRead.insert(i);
}
for (auto i : other.globalsWritten) {
globalsWritten.insert(i);
}
for (auto i : other.breakTargets) {
breakTargets.insert(i);
}
}
bool checkPre(Expression* curr) {
if (curr->is<Loop>()) {
branchesOut = true;
return true;
}
return false;
}
bool checkPost(Expression* curr) {
visit(curr);
if (curr->is<Loop>()) {
branchesOut = true;
}
return hasAnything();
}
std::set<Name> breakTargets;
void visitBlock(Block* curr) {
if (curr->name.is()) {
breakTargets.erase(curr->name); }
}
void visitIf(If* curr) {}
void visitLoop(Loop* curr) {
if (curr->name.is()) {
breakTargets.erase(curr->name); }
if (curr->type == Type::unreachable) {
branchesOut = true;
}
}
void visitBreak(Break* curr) { breakTargets.insert(curr->name); }
void visitSwitch(Switch* curr) {
for (auto name : curr->targets) {
breakTargets.insert(name);
}
breakTargets.insert(curr->default_);
}
void visitCall(Call* curr) {
calls = true;
if (features.hasExceptionHandling() && tryDepth == 0) {
throws = true;
}
if (curr->isReturn) {
branchesOut = true;
}
if (debugInfo) {
branchesOut = true;
}
}
void visitCallIndirect(CallIndirect* curr) {
calls = true;
if (features.hasExceptionHandling() && tryDepth == 0) {
throws = true;
}
if (curr->isReturn) {
branchesOut = true;
}
}
void visitLocalGet(LocalGet* curr) { localsRead.insert(curr->index); }
void visitLocalSet(LocalSet* curr) { localsWritten.insert(curr->index); }
void visitGlobalGet(GlobalGet* curr) { globalsRead.insert(curr->name); }
void visitGlobalSet(GlobalSet* curr) { globalsWritten.insert(curr->name); }
void visitLoad(Load* curr) {
readsMemory = true;
isAtomic |= curr->isAtomic;
if (!ignoreImplicitTraps) {
implicitTrap = true;
}
}
void visitStore(Store* curr) {
writesMemory = true;
isAtomic |= curr->isAtomic;
if (!ignoreImplicitTraps) {
implicitTrap = true;
}
}
void visitAtomicRMW(AtomicRMW* curr) {
readsMemory = true;
writesMemory = true;
isAtomic = true;
if (!ignoreImplicitTraps) {
implicitTrap = true;
}
}
void visitAtomicCmpxchg(AtomicCmpxchg* curr) {
readsMemory = true;
writesMemory = true;
isAtomic = true;
if (!ignoreImplicitTraps) {
implicitTrap = true;
}
}
void visitAtomicWait(AtomicWait* curr) {
readsMemory = true;
writesMemory = true;
isAtomic = true;
if (!ignoreImplicitTraps) {
implicitTrap = true;
}
}
void visitAtomicNotify(AtomicNotify* curr) {
readsMemory = true;
writesMemory = true;
isAtomic = true;
if (!ignoreImplicitTraps) {
implicitTrap = true;
}
}
void visitAtomicFence(AtomicFence* curr) {
readsMemory = true;
writesMemory = true;
isAtomic = true;
}
void visitSIMDExtract(SIMDExtract* curr) {}
void visitSIMDReplace(SIMDReplace* curr) {}
void visitSIMDShuffle(SIMDShuffle* curr) {}
void visitSIMDTernary(SIMDTernary* curr) {}
void visitSIMDShift(SIMDShift* curr) {}
void visitSIMDLoad(SIMDLoad* curr) {
readsMemory = true;
if (!ignoreImplicitTraps) {
implicitTrap = true;
}
}
void visitMemoryInit(MemoryInit* curr) {
writesMemory = true;
if (!ignoreImplicitTraps) {
implicitTrap = true;
}
}
void visitDataDrop(DataDrop* curr) {
writesMemory = true;
if (!ignoreImplicitTraps) {
implicitTrap = true;
}
}
void visitMemoryCopy(MemoryCopy* curr) {
readsMemory = true;
writesMemory = true;
if (!ignoreImplicitTraps) {
implicitTrap = true;
}
}
void visitMemoryFill(MemoryFill* curr) {
writesMemory = true;
if (!ignoreImplicitTraps) {
implicitTrap = true;
}
}
void visitConst(Const* curr) {}
void visitUnary(Unary* curr) {
if (!ignoreImplicitTraps) {
switch (curr->op) {
case TruncSFloat32ToInt32:
case TruncSFloat32ToInt64:
case TruncUFloat32ToInt32:
case TruncUFloat32ToInt64:
case TruncSFloat64ToInt32:
case TruncSFloat64ToInt64:
case TruncUFloat64ToInt32:
case TruncUFloat64ToInt64: {
implicitTrap = true;
break;
}
default: {
}
}
}
}
void visitBinary(Binary* curr) {
if (!ignoreImplicitTraps) {
switch (curr->op) {
case DivSInt32:
case DivUInt32:
case RemSInt32:
case RemUInt32:
case DivSInt64:
case DivUInt64:
case RemSInt64:
case RemUInt64: {
implicitTrap = true;
break;
}
default: {
}
}
}
}
void visitSelect(Select* curr) {}
void visitDrop(Drop* curr) {}
void visitReturn(Return* curr) { branchesOut = true; }
void visitMemorySize(MemorySize* curr) {
readsMemory = true;
isAtomic = true;
}
void visitMemoryGrow(MemoryGrow* curr) {
calls = true;
readsMemory = true;
writesMemory = true;
isAtomic = true;
}
void visitRefNull(RefNull* curr) {}
void visitRefIsNull(RefIsNull* curr) {}
void visitRefFunc(RefFunc* curr) {}
void visitRefEq(RefEq* curr) {}
void visitTry(Try* curr) {}
void visitThrow(Throw* curr) {
if (tryDepth == 0) {
throws = true;
}
}
void visitRethrow(Rethrow* curr) {
if (tryDepth == 0) {
throws = true;
}
if (!ignoreImplicitTraps) { implicitTrap = true;
}
}
void visitBrOnExn(BrOnExn* curr) {
breakTargets.insert(curr->name);
if (!ignoreImplicitTraps) { implicitTrap = true;
}
}
void visitNop(Nop* curr) {}
void visitUnreachable(Unreachable* curr) { branchesOut = true; }
void visitPop(Pop* curr) {
if (catchDepth == 0) {
danglingPop = true;
}
}
void visitTupleMake(TupleMake* curr) {}
void visitTupleExtract(TupleExtract* curr) {}
void visitI31New(I31New* curr) {}
void visitI31Get(I31Get* curr) {}
void visitRefTest(RefTest* curr) { WASM_UNREACHABLE("TODO (gc): ref.test"); }
void visitRefCast(RefCast* curr) { WASM_UNREACHABLE("TODO (gc): ref.cast"); }
void visitBrOnCast(BrOnCast* curr) {
WASM_UNREACHABLE("TODO (gc): br_on_cast");
}
void visitRttCanon(RttCanon* curr) {
WASM_UNREACHABLE("TODO (gc): rtt.canon");
}
void visitRttSub(RttSub* curr) { WASM_UNREACHABLE("TODO (gc): rtt.sub"); }
void visitStructNew(StructNew* curr) {
WASM_UNREACHABLE("TODO (gc): struct.new");
}
void visitStructGet(StructGet* curr) {
WASM_UNREACHABLE("TODO (gc): struct.get");
}
void visitStructSet(StructSet* curr) {
WASM_UNREACHABLE("TODO (gc): struct.set");
}
void visitArrayNew(ArrayNew* curr) {
WASM_UNREACHABLE("TODO (gc): array.new");
}
void visitArrayGet(ArrayGet* curr) {
WASM_UNREACHABLE("TODO (gc): array.get");
}
void visitArraySet(ArraySet* curr) {
WASM_UNREACHABLE("TODO (gc): array.set");
}
void visitArrayLen(ArrayLen* curr) {
WASM_UNREACHABLE("TODO (gc): array.len");
}
static bool canReorder(const PassOptions& passOptions,
FeatureSet features,
Expression* a,
Expression* b) {
EffectAnalyzer aEffects(passOptions, features, a);
EffectAnalyzer bEffects(passOptions, features, b);
return !aEffects.invalidates(bEffects);
}
enum SideEffects : uint32_t {
None = 0,
Branches = 1 << 0,
Calls = 1 << 1,
ReadsLocal = 1 << 2,
WritesLocal = 1 << 3,
ReadsGlobal = 1 << 4,
WritesGlobal = 1 << 5,
ReadsMemory = 1 << 6,
WritesMemory = 1 << 7,
ImplicitTrap = 1 << 8,
IsAtomic = 1 << 9,
Throws = 1 << 10,
DanglingPop = 1 << 11,
Any = (1 << 12) - 1
};
uint32_t getSideEffects() const {
uint32_t effects = 0;
if (branchesOut || hasExternalBreakTargets()) {
effects |= SideEffects::Branches;
}
if (calls) {
effects |= SideEffects::Calls;
}
if (localsRead.size() > 0) {
effects |= SideEffects::ReadsLocal;
}
if (localsWritten.size() > 0) {
effects |= SideEffects::WritesLocal;
}
if (globalsRead.size() > 0) {
effects |= SideEffects::ReadsGlobal;
}
if (globalsWritten.size() > 0) {
effects |= SideEffects::WritesGlobal;
}
if (readsMemory) {
effects |= SideEffects::ReadsMemory;
}
if (writesMemory) {
effects |= SideEffects::WritesMemory;
}
if (implicitTrap) {
effects |= SideEffects::ImplicitTrap;
}
if (isAtomic) {
effects |= SideEffects::IsAtomic;
}
if (throws) {
effects |= SideEffects::Throws;
}
if (danglingPop) {
effects |= SideEffects::DanglingPop;
}
return effects;
}
void ignoreBranches() {
branchesOut = false;
breakTargets.clear();
}
};
}
#endif