| /* | |
| * Copyright 2021 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_ir_linear_execution_h | |
| #define wasm_ir_linear_execution_h | |
| #include "ir/properties.h" | |
| #include "wasm-traversal.h" | |
| #include "wasm.h" | |
| namespace wasm { | |
| // Traversal in the order of execution. This is quick and simple, but | |
| // does not provide the same comprehensive information that a full | |
| // conversion to basic blocks would. What it does give is a quick | |
| // way to view straightline execution traces, i.e., that have no | |
| // branching. This can let optimizations get most of what they | |
| // want without the cost of creating another AST. | |
| // | |
| // When execution is no longer linear, this notifies via a call | |
| // to noteNonLinear(). | |
| template<typename SubType, typename VisitorType = Visitor<SubType>> | |
| struct LinearExecutionWalker : public PostWalker<SubType, VisitorType> { | |
| LinearExecutionWalker() = default; | |
| // subclasses should implement this | |
| void noteNonLinear(Expression* curr) { abort(); } | |
| static void doNoteNonLinear(SubType* self, Expression** currp) { | |
| self->noteNonLinear(*currp); | |
| } | |
| // Optionally, we can connect adjacent basic blocks. "Adjacent" here means | |
| // that the first branches to the second, and that there is no other code in | |
| // between them. As a result, the first dominates the second, but it might not | |
| // reach it. | |
| // | |
| // For example, a call may branch if exceptions are enabled, but if this | |
| // option is flipped on then we will *not* call doNoteNonLinear on the call: | |
| // | |
| // ..A.. | |
| // call(); | |
| // ..B.. | |
| // | |
| // As a result, we'd consider A and B to be together. Another example is an | |
| // if: | |
| // | |
| // ..A.. | |
| // if | |
| // ..B.. | |
| // else | |
| // ..C.. | |
| // end | |
| // | |
| // Here we will connect A and B, but *not* A and C (there is code in between) | |
| // or B and C (they do not branch to each other). | |
| // | |
| // As the if case shows, this can be useful for cases where we want to look at | |
| // dominated blocks with their dominator, but it only handles the trivial | |
| // adjacent cases of such dominance. Passes should generally uses a full CFG | |
| // and dominator tree for this, but this option does help some very common | |
| // cases (calls, if without an else) and it has very low overhead (we still | |
| // only do a simple postorder walk on the IR, no CFG is constructed, etc.). | |
| bool connectAdjacentBlocks = false; | |
| static void scan(SubType* self, Expression** currp) { | |
| Expression* curr = *currp; | |
| auto handleCall = [&](bool isReturn, bool refutesThrowEffect) { | |
| bool mayThrow = !self->getModule() || | |
| self->getModule()->features.hasExceptionHandling(); | |
| mayThrow = mayThrow && !refutesThrowEffect; | |
| if (!self->connectAdjacentBlocks) { | |
| // Control is nonlinear if we return or throw. Traps don't need to be | |
| // taken into account since they don't break control flow in a way | |
| // that's observable. | |
| if (mayThrow || isReturn) { | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| } | |
| } | |
| // Scan the children normally. | |
| PostWalker<SubType, VisitorType>::scan(self, currp); | |
| }; | |
| switch (curr->_id) { | |
| case Expression::Id::InvalidId: | |
| WASM_UNREACHABLE("bad id"); | |
| case Expression::Id::BlockId: { | |
| self->pushTask(SubType::doVisitBlock, currp); | |
| if (curr->cast<Block>()->name.is()) { | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| } | |
| auto& list = curr->cast<Block>()->list; | |
| for (int i = int(list.size()) - 1; i >= 0; i--) { | |
| self->pushTask(SubType::scan, &list[i]); | |
| } | |
| break; | |
| } | |
| case Expression::Id::IfId: { | |
| self->pushTask(SubType::doVisitIf, currp); | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| self->maybePushTask(SubType::scan, &curr->cast<If>()->ifFalse); | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| self->pushTask(SubType::scan, &curr->cast<If>()->ifTrue); | |
| if (!self->connectAdjacentBlocks) { | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| } | |
| self->pushTask(SubType::scan, &curr->cast<If>()->condition); | |
| break; | |
| } | |
| case Expression::Id::LoopId: { | |
| self->pushTask(SubType::doVisitLoop, currp); | |
| self->pushTask(SubType::scan, &curr->cast<Loop>()->body); | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| break; | |
| } | |
| case Expression::Id::BreakId: { | |
| self->pushTask(SubType::doVisitBreak, currp); | |
| auto* br = curr->cast<Break>(); | |
| // If there is no condition then we note non-linearity as the code after | |
| // us is unreachable anyhow (we do the same for Switch, Return, etc.). | |
| // If there is a condition, then we note or do not note depending on | |
| // whether we allow adjacent blocks. | |
| if (!br->condition || !self->connectAdjacentBlocks) { | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| } | |
| self->maybePushTask(SubType::scan, &br->condition); | |
| self->maybePushTask(SubType::scan, &br->value); | |
| break; | |
| } | |
| case Expression::Id::SwitchId: { | |
| self->pushTask(SubType::doVisitSwitch, currp); | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| self->pushTask(SubType::scan, &curr->cast<Switch>()->condition); | |
| self->maybePushTask(SubType::scan, &curr->cast<Switch>()->value); | |
| break; | |
| } | |
| case Expression::Id::ReturnId: { | |
| self->pushTask(SubType::doVisitReturn, currp); | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| self->maybePushTask(SubType::scan, &curr->cast<Return>()->value); | |
| break; | |
| } | |
| case Expression::Id::CallId: { | |
| auto* call = curr->cast<Call>(); | |
| bool refutesThrowEffect = false; | |
| if (self->getModule()) { | |
| auto* func = self->getModule()->getFunctionOrNull(call->target); | |
| // TODO: `func` might not exist here because of #8753. Fix this | |
| // and remove the null check. | |
| if (func && func->effects) { | |
| refutesThrowEffect = !func->effects->throws_; | |
| } | |
| } | |
| handleCall(call->isReturn, refutesThrowEffect); | |
| break; | |
| } | |
| case Expression::Id::CallRefId: { | |
| auto* callRef = curr->cast<CallRef>(); | |
| bool refutesThrowEffect = [&]() { | |
| if (!self->getModule()) { | |
| return false; | |
| } | |
| if (!callRef->target->type.isRef()) { | |
| // This is an unreachable, so no throws effect. | |
| return true; | |
| } | |
| auto* effects = find_or_null(self->getModule()->indirectCallEffects, | |
| callRef->target->type.getHeapType()); | |
| if (!effects) { | |
| return false; | |
| } | |
| return !(*effects)->throws_; | |
| }(); | |
| handleCall(callRef->isReturn, refutesThrowEffect); | |
| break; | |
| } | |
| case Expression::Id::CallIndirectId: { | |
| auto* callIndirect = curr->cast<CallIndirect>(); | |
| bool refutesThrowEffect = false; | |
| if (self->getModule()) { | |
| if (auto* effects = find_or_null( | |
| self->getModule()->indirectCallEffects, callIndirect->heapType); | |
| effects) { | |
| refutesThrowEffect = !(*effects)->throws_; | |
| } | |
| } | |
| handleCall(callIndirect->isReturn, refutesThrowEffect); | |
| break; | |
| } | |
| case Expression::Id::TryId: { | |
| self->pushTask(SubType::doVisitTry, currp); | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| auto& list = curr->cast<Try>()->catchBodies; | |
| for (int i = int(list.size()) - 1; i >= 0; i--) { | |
| self->pushTask(SubType::scan, &list[i]); | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| } | |
| self->pushTask(SubType::scan, &curr->cast<Try>()->body); | |
| break; | |
| } | |
| case Expression::Id::TryTableId: { | |
| self->pushTask(SubType::doVisitTryTable, currp); | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| self->pushTask(SubType::scan, &curr->cast<TryTable>()->body); | |
| break; | |
| } | |
| case Expression::Id::ThrowId: { | |
| self->pushTask(SubType::doVisitThrow, currp); | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| auto& list = curr->cast<Throw>()->operands; | |
| for (int i = int(list.size()) - 1; i >= 0; i--) { | |
| self->pushTask(SubType::scan, &list[i]); | |
| } | |
| break; | |
| } | |
| case Expression::Id::RethrowId: { | |
| self->pushTask(SubType::doVisitRethrow, currp); | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| break; | |
| } | |
| case Expression::Id::UnreachableId: { | |
| self->pushTask(SubType::doVisitUnreachable, currp); | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| break; | |
| } | |
| case Expression::Id::BrOnId: { | |
| self->pushTask(SubType::doVisitBrOn, currp); | |
| if (!self->connectAdjacentBlocks) { | |
| self->pushTask(SubType::doNoteNonLinear, currp); | |
| } | |
| self->maybePushTask(SubType::scan, &curr->cast<BrOn>()->desc); | |
| self->pushTask(SubType::scan, &curr->cast<BrOn>()->ref); | |
| break; | |
| } | |
| default: { | |
| // All relevant things should have been handled. | |
| assert(!Properties::isControlFlowStructure(curr)); | |
| assert(!Properties::isBranch(curr)); | |
| // other node types do not have control flow, use regular post-order | |
| PostWalker<SubType, VisitorType>::scan(self, currp); | |
| } | |
| } | |
| } | |
| }; | |
| } // namespace wasm | |
| #endif // wasm_ir_linear_execution_h |