Skip to content

Commit 2670565

Browse files
authored
[Coroutines] Move materialization code into its own utils (llvm#108240)
* Move materialization out of CoroFrame to MaterializationUtils.h * Move spill related utilities that were used by materialization to SpillUtils * Move isSuspendBlock (needed by materialization) to CoroInternal See RFC for more info: https://discourse.llvm.org/t/rfc-abi-objects-for-coroutines/81057
1 parent 0989a77 commit 2670565

File tree

8 files changed

+351
-297
lines changed

8 files changed

+351
-297
lines changed

llvm/lib/Transforms/Coroutines/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ add_llvm_component_library(LLVMCoroutines
99
CoroSplit.cpp
1010
SuspendCrossingInfo.cpp
1111
SpillUtils.cpp
12+
MaterializationUtils.cpp
1213

1314
ADDITIONAL_HEADER_DIRS
1415
${LLVM_MAIN_INCLUDE_DIR}/llvm/Transforms/Coroutines

llvm/lib/Transforms/Coroutines/CoroFrame.cpp

Lines changed: 6 additions & 290 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616
//===----------------------------------------------------------------------===//
1717

1818
#include "CoroInternal.h"
19+
#include "MaterializationUtils.h"
1920
#include "SpillUtils.h"
2021
#include "SuspendCrossingInfo.h"
2122
#include "llvm/ADT/BitVector.h"
22-
#include "llvm/ADT/PostOrderIterator.h"
2323
#include "llvm/ADT/ScopeExit.h"
2424
#include "llvm/ADT/SmallString.h"
2525
#include "llvm/Analysis/StackLifetime.h"
@@ -36,135 +36,12 @@
3636
#include "llvm/Transforms/Utils/Local.h"
3737
#include "llvm/Transforms/Utils/PromoteMemToReg.h"
3838
#include <algorithm>
39-
#include <deque>
4039
#include <optional>
4140

4241
using namespace llvm;
4342

4443
extern cl::opt<bool> UseNewDbgInfoFormat;
4544

46-
// The "coro-suspend-crossing" flag is very noisy. There is another debug type,
47-
// "coro-frame", which results in leaner debug spew.
48-
#define DEBUG_TYPE "coro-suspend-crossing"
49-
50-
namespace {
51-
52-
// RematGraph is used to construct a DAG for rematerializable instructions
53-
// When the constructor is invoked with a candidate instruction (which is
54-
// materializable) it builds a DAG of materializable instructions from that
55-
// point.
56-
// Typically, for each instruction identified as re-materializable across a
57-
// suspend point, a RematGraph will be created.
58-
struct RematGraph {
59-
// Each RematNode in the graph contains the edges to instructions providing
60-
// operands in the current node.
61-
struct RematNode {
62-
Instruction *Node;
63-
SmallVector<RematNode *> Operands;
64-
RematNode() = default;
65-
RematNode(Instruction *V) : Node(V) {}
66-
};
67-
68-
RematNode *EntryNode;
69-
using RematNodeMap =
70-
SmallMapVector<Instruction *, std::unique_ptr<RematNode>, 8>;
71-
RematNodeMap Remats;
72-
const std::function<bool(Instruction &)> &MaterializableCallback;
73-
SuspendCrossingInfo &Checker;
74-
75-
RematGraph(const std::function<bool(Instruction &)> &MaterializableCallback,
76-
Instruction *I, SuspendCrossingInfo &Checker)
77-
: MaterializableCallback(MaterializableCallback), Checker(Checker) {
78-
std::unique_ptr<RematNode> FirstNode = std::make_unique<RematNode>(I);
79-
EntryNode = FirstNode.get();
80-
std::deque<std::unique_ptr<RematNode>> WorkList;
81-
addNode(std::move(FirstNode), WorkList, cast<User>(I));
82-
while (WorkList.size()) {
83-
std::unique_ptr<RematNode> N = std::move(WorkList.front());
84-
WorkList.pop_front();
85-
addNode(std::move(N), WorkList, cast<User>(I));
86-
}
87-
}
88-
89-
void addNode(std::unique_ptr<RematNode> NUPtr,
90-
std::deque<std::unique_ptr<RematNode>> &WorkList,
91-
User *FirstUse) {
92-
RematNode *N = NUPtr.get();
93-
if (Remats.count(N->Node))
94-
return;
95-
96-
// We haven't see this node yet - add to the list
97-
Remats[N->Node] = std::move(NUPtr);
98-
for (auto &Def : N->Node->operands()) {
99-
Instruction *D = dyn_cast<Instruction>(Def.get());
100-
if (!D || !MaterializableCallback(*D) ||
101-
!Checker.isDefinitionAcrossSuspend(*D, FirstUse))
102-
continue;
103-
104-
if (Remats.count(D)) {
105-
// Already have this in the graph
106-
N->Operands.push_back(Remats[D].get());
107-
continue;
108-
}
109-
110-
bool NoMatch = true;
111-
for (auto &I : WorkList) {
112-
if (I->Node == D) {
113-
NoMatch = false;
114-
N->Operands.push_back(I.get());
115-
break;
116-
}
117-
}
118-
if (NoMatch) {
119-
// Create a new node
120-
std::unique_ptr<RematNode> ChildNode = std::make_unique<RematNode>(D);
121-
N->Operands.push_back(ChildNode.get());
122-
WorkList.push_back(std::move(ChildNode));
123-
}
124-
}
125-
}
126-
127-
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
128-
static std::string getBasicBlockLabel(const BasicBlock *BB) {
129-
if (BB->hasName())
130-
return BB->getName().str();
131-
132-
std::string S;
133-
raw_string_ostream OS(S);
134-
BB->printAsOperand(OS, false);
135-
return OS.str().substr(1);
136-
}
137-
138-
void dump() const {
139-
dbgs() << "Entry (";
140-
dbgs() << getBasicBlockLabel(EntryNode->Node->getParent());
141-
dbgs() << ") : " << *EntryNode->Node << "\n";
142-
for (auto &E : Remats) {
143-
dbgs() << *(E.first) << "\n";
144-
for (RematNode *U : E.second->Operands)
145-
dbgs() << " " << *U->Node << "\n";
146-
}
147-
}
148-
#endif
149-
};
150-
} // end anonymous namespace
151-
152-
namespace llvm {
153-
154-
template <> struct GraphTraits<RematGraph *> {
155-
using NodeRef = RematGraph::RematNode *;
156-
using ChildIteratorType = RematGraph::RematNode **;
157-
158-
static NodeRef getEntryNode(RematGraph *G) { return G->EntryNode; }
159-
static ChildIteratorType child_begin(NodeRef N) {
160-
return N->Operands.begin();
161-
}
162-
static ChildIteratorType child_end(NodeRef N) { return N->Operands.end(); }
163-
};
164-
165-
} // end namespace llvm
166-
167-
#undef DEBUG_TYPE // "coro-suspend-crossing"
16845
#define DEBUG_TYPE "coro-frame"
16946

17047
namespace {
@@ -268,15 +145,6 @@ static void dumpSpills(StringRef Title, const coro::SpillInfo &Spills) {
268145
I->dump();
269146
}
270147
}
271-
static void dumpRemats(
272-
StringRef Title,
273-
const SmallMapVector<Instruction *, std::unique_ptr<RematGraph>, 8> &RM) {
274-
dbgs() << "------------- " << Title << "--------------\n";
275-
for (const auto &E : RM) {
276-
E.second->dump();
277-
dbgs() << "--\n";
278-
}
279-
}
280148

281149
static void dumpAllocas(const SmallVectorImpl<coro::AllocaInfo> &Allocas) {
282150
dbgs() << "------------- Allocas --------------\n";
@@ -1634,93 +1502,6 @@ static void rewritePHIs(Function &F) {
16341502
rewritePHIs(*BB);
16351503
}
16361504

1637-
/// Default materializable callback
1638-
// Check for instructions that we can recreate on resume as opposed to spill
1639-
// the result into a coroutine frame.
1640-
bool coro::defaultMaterializable(Instruction &V) {
1641-
return (isa<CastInst>(&V) || isa<GetElementPtrInst>(&V) ||
1642-
isa<BinaryOperator>(&V) || isa<CmpInst>(&V) || isa<SelectInst>(&V));
1643-
}
1644-
1645-
// For each instruction identified as materializable across the suspend point,
1646-
// and its associated DAG of other rematerializable instructions,
1647-
// recreate the DAG of instructions after the suspend point.
1648-
static void rewriteMaterializableInstructions(
1649-
const SmallMapVector<Instruction *, std::unique_ptr<RematGraph>, 8>
1650-
&AllRemats) {
1651-
// This has to be done in 2 phases
1652-
// Do the remats and record the required defs to be replaced in the
1653-
// original use instructions
1654-
// Once all the remats are complete, replace the uses in the final
1655-
// instructions with the new defs
1656-
typedef struct {
1657-
Instruction *Use;
1658-
Instruction *Def;
1659-
Instruction *Remat;
1660-
} ProcessNode;
1661-
1662-
SmallVector<ProcessNode> FinalInstructionsToProcess;
1663-
1664-
for (const auto &E : AllRemats) {
1665-
Instruction *Use = E.first;
1666-
Instruction *CurrentMaterialization = nullptr;
1667-
RematGraph *RG = E.second.get();
1668-
ReversePostOrderTraversal<RematGraph *> RPOT(RG);
1669-
SmallVector<Instruction *> InstructionsToProcess;
1670-
1671-
// If the target use is actually a suspend instruction then we have to
1672-
// insert the remats into the end of the predecessor (there should only be
1673-
// one). This is so that suspend blocks always have the suspend instruction
1674-
// as the first instruction.
1675-
auto InsertPoint = &*Use->getParent()->getFirstInsertionPt();
1676-
if (isa<AnyCoroSuspendInst>(Use)) {
1677-
BasicBlock *SuspendPredecessorBlock =
1678-
Use->getParent()->getSinglePredecessor();
1679-
assert(SuspendPredecessorBlock && "malformed coro suspend instruction");
1680-
InsertPoint = SuspendPredecessorBlock->getTerminator();
1681-
}
1682-
1683-
// Note: skip the first instruction as this is the actual use that we're
1684-
// rematerializing everything for.
1685-
auto I = RPOT.begin();
1686-
++I;
1687-
for (; I != RPOT.end(); ++I) {
1688-
Instruction *D = (*I)->Node;
1689-
CurrentMaterialization = D->clone();
1690-
CurrentMaterialization->setName(D->getName());
1691-
CurrentMaterialization->insertBefore(InsertPoint);
1692-
InsertPoint = CurrentMaterialization;
1693-
1694-
// Replace all uses of Def in the instructions being added as part of this
1695-
// rematerialization group
1696-
for (auto &I : InstructionsToProcess)
1697-
I->replaceUsesOfWith(D, CurrentMaterialization);
1698-
1699-
// Don't replace the final use at this point as this can cause problems
1700-
// for other materializations. Instead, for any final use that uses a
1701-
// define that's being rematerialized, record the replace values
1702-
for (unsigned i = 0, E = Use->getNumOperands(); i != E; ++i)
1703-
if (Use->getOperand(i) == D) // Is this operand pointing to oldval?
1704-
FinalInstructionsToProcess.push_back(
1705-
{Use, D, CurrentMaterialization});
1706-
1707-
InstructionsToProcess.push_back(CurrentMaterialization);
1708-
}
1709-
}
1710-
1711-
// Finally, replace the uses with the defines that we've just rematerialized
1712-
for (auto &R : FinalInstructionsToProcess) {
1713-
if (auto *PN = dyn_cast<PHINode>(R.Use)) {
1714-
assert(PN->getNumIncomingValues() == 1 && "unexpected number of incoming "
1715-
"values in the PHINode");
1716-
PN->replaceAllUsesWith(R.Remat);
1717-
PN->eraseFromParent();
1718-
continue;
1719-
}
1720-
R.Use->replaceUsesOfWith(R.Def, R.Remat);
1721-
}
1722-
}
1723-
17241505
// Splits the block at a particular instruction unless it is the first
17251506
// instruction in the block with a single predecessor.
17261507
static BasicBlock *splitBlockIfNotFirst(Instruction *I, const Twine &Name) {
@@ -1741,10 +1522,6 @@ static void splitAround(Instruction *I, const Twine &Name) {
17411522
splitBlockIfNotFirst(I->getNextNode(), "After" + Name);
17421523
}
17431524

1744-
static bool isSuspendBlock(BasicBlock *BB) {
1745-
return isa<AnyCoroSuspendInst>(BB->front());
1746-
}
1747-
17481525
/// After we split the coroutine, will the given basic block be along
17491526
/// an obvious exit path for the resumption function?
17501527
static bool willLeaveFunctionImmediatelyAfter(BasicBlock *BB,
@@ -1754,7 +1531,7 @@ static bool willLeaveFunctionImmediatelyAfter(BasicBlock *BB,
17541531
if (depth == 0) return false;
17551532

17561533
// If this is a suspend block, we're about to exit the resumption function.
1757-
if (isSuspendBlock(BB))
1534+
if (coro::isSuspendBlock(BB))
17581535
return true;
17591536

17601537
// Recurse into the successors.
@@ -1995,7 +1772,8 @@ static void sinkLifetimeStartMarkers(Function &F, coro::Shape &Shape,
19951772
DomSet.insert(&F.getEntryBlock());
19961773
for (auto *CSI : Shape.CoroSuspends) {
19971774
BasicBlock *SuspendBlock = CSI->getParent();
1998-
assert(isSuspendBlock(SuspendBlock) && SuspendBlock->getSingleSuccessor() &&
1775+
assert(coro::isSuspendBlock(SuspendBlock) &&
1776+
SuspendBlock->getSingleSuccessor() &&
19991777
"should have split coro.suspend into its own block");
20001778
DomSet.insert(SuspendBlock->getSingleSuccessor());
20011779
}
@@ -2227,68 +2005,6 @@ void coro::salvageDebugInfo(
22272005
}
22282006
}
22292007

2230-
static void doRematerializations(
2231-
Function &F, SuspendCrossingInfo &Checker,
2232-
const std::function<bool(Instruction &)> &MaterializableCallback) {
2233-
if (F.hasOptNone())
2234-
return;
2235-
2236-
coro::SpillInfo Spills;
2237-
2238-
// See if there are materializable instructions across suspend points
2239-
// We record these as the starting point to also identify materializable
2240-
// defs of uses in these operations
2241-
for (Instruction &I : instructions(F)) {
2242-
if (!MaterializableCallback(I))
2243-
continue;
2244-
for (User *U : I.users())
2245-
if (Checker.isDefinitionAcrossSuspend(I, U))
2246-
Spills[&I].push_back(cast<Instruction>(U));
2247-
}
2248-
2249-
// Process each of the identified rematerializable instructions
2250-
// and add predecessor instructions that can also be rematerialized.
2251-
// This is actually a graph of instructions since we could potentially
2252-
// have multiple uses of a def in the set of predecessor instructions.
2253-
// The approach here is to maintain a graph of instructions for each bottom
2254-
// level instruction - where we have a unique set of instructions (nodes)
2255-
// and edges between them. We then walk the graph in reverse post-dominator
2256-
// order to insert them past the suspend point, but ensure that ordering is
2257-
// correct. We also rely on CSE removing duplicate defs for remats of
2258-
// different instructions with a def in common (rather than maintaining more
2259-
// complex graphs for each suspend point)
2260-
2261-
// We can do this by adding new nodes to the list for each suspend
2262-
// point. Then using standard GraphTraits to give a reverse post-order
2263-
// traversal when we insert the nodes after the suspend
2264-
SmallMapVector<Instruction *, std::unique_ptr<RematGraph>, 8> AllRemats;
2265-
for (auto &E : Spills) {
2266-
for (Instruction *U : E.second) {
2267-
// Don't process a user twice (this can happen if the instruction uses
2268-
// more than one rematerializable def)
2269-
if (AllRemats.count(U))
2270-
continue;
2271-
2272-
// Constructor creates the whole RematGraph for the given Use
2273-
auto RematUPtr =
2274-
std::make_unique<RematGraph>(MaterializableCallback, U, Checker);
2275-
2276-
LLVM_DEBUG(dbgs() << "***** Next remat group *****\n";
2277-
ReversePostOrderTraversal<RematGraph *> RPOT(RematUPtr.get());
2278-
for (auto I = RPOT.begin(); I != RPOT.end();
2279-
++I) { (*I)->Node->dump(); } dbgs()
2280-
<< "\n";);
2281-
2282-
AllRemats[U] = std::move(RematUPtr);
2283-
}
2284-
}
2285-
2286-
// Rewrite materializable instructions to be materialized at the use
2287-
// point.
2288-
LLVM_DEBUG(dumpRemats("Materializations", AllRemats));
2289-
rewriteMaterializableInstructions(AllRemats);
2290-
}
2291-
22922008
void coro::normalizeCoroutine(Function &F, coro::Shape &Shape,
22932009
TargetTransformInfo &TTI) {
22942010
// Don't eliminate swifterror in async functions that won't be split.
@@ -2324,8 +2040,8 @@ void coro::normalizeCoroutine(Function &F, coro::Shape &Shape,
23242040
IRBuilder<> Builder(AsyncEnd);
23252041
SmallVector<Value *, 8> Args(AsyncEnd->args());
23262042
auto Arguments = ArrayRef<Value *>(Args).drop_front(3);
2327-
auto *Call = createMustTailCall(AsyncEnd->getDebugLoc(), MustTailCallFn,
2328-
TTI, Arguments, Builder);
2043+
auto *Call = coro::createMustTailCall(
2044+
AsyncEnd->getDebugLoc(), MustTailCallFn, TTI, Arguments, Builder);
23292045
splitAround(Call, "MustTailCall.Before.CoroEnd");
23302046
}
23312047
}

llvm/lib/Transforms/Coroutines/CoroInternal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class CallGraph;
2121

2222
namespace coro {
2323

24+
bool isSuspendBlock(BasicBlock *BB);
2425
bool declaresAnyIntrinsic(const Module &M);
2526
bool declaresIntrinsics(const Module &M,
2627
const std::initializer_list<StringRef>);

llvm/lib/Transforms/Coroutines/Coroutines.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ static bool isCoroutineIntrinsicName(StringRef Name) {
100100
}
101101
#endif
102102

103+
bool coro::isSuspendBlock(BasicBlock *BB) {
104+
return isa<AnyCoroSuspendInst>(BB->front());
105+
}
106+
103107
bool coro::declaresAnyIntrinsic(const Module &M) {
104108
for (StringRef Name : CoroIntrinsics) {
105109
assert(isCoroutineIntrinsicName(Name) && "not a coroutine intrinsic");

0 commit comments

Comments
 (0)