//===- SuspendCrossingInfo.cpp - Utility for suspend crossing values ------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // The SuspendCrossingInfo maintains data that allows to answer a question // whether given two BasicBlocks A and B there is a path from A to B that // passes through a suspend point. Note, SuspendCrossingInfo is invalidated // by changes to the CFG including adding/removing BBs due to its use of BB // ptrs in the BlockToIndexMapping. //===----------------------------------------------------------------------===// #include "llvm/Transforms/Coroutines/SuspendCrossingInfo.h" #include "llvm/IR/ModuleSlotTracker.h" // The "coro-suspend-crossing" flag is very noisy. There is another debug type, // "coro-frame", which results in leaner debug spew. #define DEBUG_TYPE "coro-suspend-crossing" namespace llvm { #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) static void dumpBasicBlockLabel(const BasicBlock *BB, ModuleSlotTracker &MST) { if (BB->hasName()) { dbgs() << BB->getName(); return; } dbgs() << MST.getLocalSlot(BB); } LLVM_DUMP_METHOD void SuspendCrossingInfo::dump(StringRef Label, BitVector const &BV, const ReversePostOrderTraversal &RPOT, ModuleSlotTracker &MST) const { dbgs() << Label << ":"; for (const BasicBlock *BB : RPOT) { auto BBNo = Mapping.blockToIndex(BB); if (BV[BBNo]) { dbgs() << " "; dumpBasicBlockLabel(BB, MST); } } dbgs() << "\n"; } LLVM_DUMP_METHOD void SuspendCrossingInfo::dump() const { if (Block.empty()) return; BasicBlock *const B = Mapping.indexToBlock(0); Function *F = B->getParent(); ModuleSlotTracker MST(F->getParent()); MST.incorporateFunction(*F); ReversePostOrderTraversal RPOT(F); for (const BasicBlock *BB : RPOT) { auto BBNo = Mapping.blockToIndex(BB); dumpBasicBlockLabel(BB, MST); dbgs() << ":\n"; dump(" Consumes", Block[BBNo].Consumes, RPOT, MST); dump(" Kills", Block[BBNo].Kills, RPOT, MST); } dbgs() << "\n"; } #endif bool SuspendCrossingInfo::hasPathCrossingSuspendPoint(BasicBlock *From, BasicBlock *To) const { size_t const FromIndex = Mapping.blockToIndex(From); size_t const ToIndex = Mapping.blockToIndex(To); bool const Result = Block[ToIndex].Kills[FromIndex]; LLVM_DEBUG(if (Result) dbgs() << From->getName() << " => " << To->getName() << " crosses suspend point\n"); return Result; } bool SuspendCrossingInfo::hasPathOrLoopCrossingSuspendPoint( BasicBlock *From, BasicBlock *To) const { size_t const FromIndex = Mapping.blockToIndex(From); size_t const ToIndex = Mapping.blockToIndex(To); bool Result = Block[ToIndex].Kills[FromIndex] || (From == To && Block[ToIndex].KillLoop); LLVM_DEBUG(if (Result) dbgs() << From->getName() << " => " << To->getName() << " crosses suspend point (path or loop)\n"); return Result; } template bool SuspendCrossingInfo::computeBlockData( const ReversePostOrderTraversal &RPOT) { bool Changed = false; for (const BasicBlock *BB : RPOT) { auto BBNo = Mapping.blockToIndex(BB); auto &B = Block[BBNo]; // We don't need to count the predecessors when initialization. if constexpr (!Initialize) // If all the predecessors of the current Block don't change, // the BlockData for the current block must not change too. if (all_of(predecessors(B), [this](BasicBlock *BB) { return !Block[Mapping.blockToIndex(BB)].Changed; })) { B.Changed = false; continue; } // Saved Consumes and Kills bitsets so that it is easy to see // if anything changed after propagation. auto SavedConsumes = B.Consumes; auto SavedKills = B.Kills; for (BasicBlock *PI : predecessors(B)) { auto PrevNo = Mapping.blockToIndex(PI); auto &P = Block[PrevNo]; // Propagate Kills and Consumes from predecessors into B. B.Consumes |= P.Consumes; B.Kills |= P.Kills; // If block P is a suspend block, it should propagate kills into block // B for every block P consumes. if (P.Suspend) B.Kills |= P.Consumes; } if (B.Suspend) { // If block B is a suspend block, it should kill all of the blocks it // consumes. B.Kills |= B.Consumes; } else if (B.End) { // If block B is an end block, it should not propagate kills as the // blocks following coro.end() are reached during initial invocation // of the coroutine while all the data are still available on the // stack or in the registers. B.Kills.reset(); } else { // This is reached when B block it not Suspend nor coro.end and it // need to make sure that it is not in the kill set. B.KillLoop |= B.Kills[BBNo]; B.Kills.reset(BBNo); } if constexpr (!Initialize) { B.Changed = (B.Kills != SavedKills) || (B.Consumes != SavedConsumes); Changed |= B.Changed; } } return Changed; } SuspendCrossingInfo::SuspendCrossingInfo( Function &F, const SmallVectorImpl &CoroSuspends, const SmallVectorImpl &CoroEnds) : Mapping(F) { const size_t N = Mapping.size(); Block.resize(N); // Initialize every block so that it consumes itself for (size_t I = 0; I < N; ++I) { auto &B = Block[I]; B.Consumes.resize(N); B.Kills.resize(N); B.Consumes.set(I); B.Changed = true; } // Mark all CoroEnd Blocks. We do not propagate Kills beyond coro.ends as // the code beyond coro.end is reachable during initial invocation of the // coroutine. for (auto *CE : CoroEnds) { // Verify CoroEnd was normalized assert(CE->getParent()->getFirstInsertionPt() == CE->getIterator() && CE->getParent()->size() <= 2 && "CoroEnd must be in its own BB"); getBlockData(CE->getParent()).End = true; } // Mark all suspend blocks and indicate that they kill everything they // consume. Note, that crossing coro.save also requires a spill, as any code // between coro.save and coro.suspend may resume the coroutine and all of the // state needs to be saved by that time. auto markSuspendBlock = [&](IntrinsicInst *BarrierInst) { BasicBlock *SuspendBlock = BarrierInst->getParent(); auto &B = getBlockData(SuspendBlock); B.Suspend = true; B.Kills |= B.Consumes; }; for (auto *CSI : CoroSuspends) { // Verify CoroSuspend was normalized assert(CSI->getParent()->getFirstInsertionPt() == CSI->getIterator() && CSI->getParent()->size() <= 2 && "CoroSuspend must be in its own BB"); markSuspendBlock(CSI); if (auto *Save = CSI->getCoroSave()) markSuspendBlock(Save); } // It is considered to be faster to use RPO traversal for forward-edges // dataflow analysis. ReversePostOrderTraversal RPOT(&F); computeBlockData(RPOT); while (computeBlockData(RPOT)) ; LLVM_DEBUG(dump()); } } // namespace llvm