LLVM 24.0.0git
CoroAnnotationElide.cpp
Go to the documentation of this file.
1//===- CoroAnnotationElide.cpp - Elide attributed safe coroutine calls ----===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// \file
10// This pass transforms all Call or Invoke instructions that are annotated
11// "coro_elide_safe" to call the `.noalloc` variant of coroutine instead.
12// The frame of the callee coroutine is allocated inside the caller. A pointer
13// to the allocated frame will be passed into the `.noalloc` ramp function.
14//
15//===----------------------------------------------------------------------===//
16
18
22#include "llvm/IR/Analysis.h"
23#include "llvm/IR/IRBuilder.h"
24#include "llvm/IR/Instruction.h"
25#include "llvm/IR/Module.h"
26#include "llvm/IR/PassManager.h"
30
31#include <cassert>
32
33using namespace llvm;
34
35#define DEBUG_TYPE "coro-annotation-elide"
36
38 "coro-elide-branch-ratio", cl::init(0.55), cl::Hidden,
39 cl::desc("Minimum BranchProbability to consider a elide a coroutine."));
41
43 for (Instruction &I : F->getEntryBlock())
44 if (!isa<AllocaInst>(&I))
45 return &I;
46 llvm_unreachable("no terminator in the entry block");
47}
48
49// Create an alloca in the caller, using FrameSize and FrameAlign as the callee
50// coroutine's activation frame.
51static Value *allocateFrameInCaller(Function *Caller, uint64_t FrameSize,
52 Align FrameAlign) {
53 LLVMContext &C = Caller->getContext();
54 BasicBlock::iterator InsertPt =
56 const DataLayout &DL = Caller->getDataLayout();
57 auto FrameTy = ArrayType::get(Type::getInt8Ty(C), FrameSize);
58 auto *Frame = new AllocaInst(FrameTy, DL.getAllocaAddrSpace(), "", InsertPt);
59 Frame->setAlignment(FrameAlign);
60 return Frame;
61}
62
63// Given a call or invoke instruction to the elide safe coroutine, this function
64// does the following:
65// - Allocate a frame for the callee coroutine in the caller using alloca.
66// - Replace the old CB with a new Call or Invoke to `NewCallee`, with the
67// pointer to the frame as an additional argument to NewCallee.
68static void processCall(CallBase *CB, Function *Caller, Function *NewCallee,
69 uint64_t FrameSize, Align FrameAlign) {
70 // TODO: generate the lifetime intrinsics for the new frame. This will require
71 // introduction of two pesudo lifetime intrinsics in the frontend around the
72 // `co_await` expression and convert them to real lifetime intrinsics here.
73 auto *FramePtr = allocateFrameInCaller(Caller, FrameSize, FrameAlign);
74 auto NewCBInsertPt = CB->getIterator();
75 llvm::CallBase *NewCB = nullptr;
77 NewArgs.append(CB->arg_begin(), CB->arg_end());
78 NewArgs.push_back(FramePtr);
79
80 if (auto *CI = dyn_cast<CallInst>(CB)) {
81 auto *NewCI = CallInst::Create(NewCallee->getFunctionType(), NewCallee,
82 NewArgs, "", NewCBInsertPt);
83 NewCI->setTailCallKind(CI->getTailCallKind());
84 NewCB = NewCI;
85 } else if (auto *II = dyn_cast<InvokeInst>(CB)) {
86 NewCB = InvokeInst::Create(NewCallee->getFunctionType(), NewCallee,
87 II->getNormalDest(), II->getUnwindDest(),
88 NewArgs, {}, "", NewCBInsertPt);
89 } else {
90 llvm_unreachable("CallBase should either be Call or Invoke!");
91 }
92
93 NewCB->setCalledFunction(NewCallee->getFunctionType(), NewCallee);
94 NewCB->setCallingConv(CB->getCallingConv());
95 NewCB->setAttributes(CB->getAttributes());
96 NewCB->setDebugLoc(CB->getDebugLoc());
97 std::copy(CB->bundle_op_info_begin(), CB->bundle_op_info_end(),
98 NewCB->bundle_op_info_begin());
99
100 NewCB->removeFnAttr(llvm::Attribute::CoroElideSafe);
101 CB->replaceAllUsesWith(NewCB);
102
104 InlineResult IR = InlineFunction(*NewCB, IFI);
105 if (IR.isSuccess()) {
106 CB->eraseFromParent();
107 } else {
108 NewCB->replaceAllUsesWith(CB);
109 NewCB->eraseFromParent();
110 }
111}
112
115 LazyCallGraph &CG,
116 CGSCCUpdateResult &UR) {
117 bool Changed = false;
118 CallGraphUpdater CGUpdater;
119 CGUpdater.initialize(CG, C, AM, UR);
120
121 auto &FAM =
122 AM.getResult<FunctionAnalysisManagerCGSCCProxy>(C, CG).getManager();
123
124 for (LazyCallGraph::Node &N : C) {
125 Function *Callee = &N.getFunction();
126 Function *NewCallee = Callee->getParent()->getFunction(
127 (Callee->getName() + ".noalloc").str());
128 if (!NewCallee)
129 continue;
130
132 for (auto *U : Callee->users()) {
133 if (auto *CB = dyn_cast<CallBase>(U)) {
134 if (CB->getCalledFunction() == Callee)
135 Users.push_back(CB);
136 }
137 }
138 auto FramePtrArgPosition = NewCallee->arg_size() - 1;
139 auto FrameSize =
140 NewCallee->getParamDereferenceableBytes(FramePtrArgPosition);
141 auto FrameAlign =
142 NewCallee->getParamAlign(FramePtrArgPosition).valueOrOne();
143
144 auto &ORE = FAM.getResult<OptimizationRemarkEmitterAnalysis>(*Callee);
145
146 for (auto *CB : Users) {
147 auto *Caller = CB->getFunction();
148 if (!Caller)
149 continue;
150
151 bool IsCallerPresplitCoroutine = Caller->isPresplitCoroutine();
152 bool HasAttr = CB->hasFnAttr(llvm::Attribute::CoroElideSafe);
153 if (IsCallerPresplitCoroutine && HasAttr) {
154 auto &BFI = FAM.getResult<BlockFrequencyAnalysis>(*Caller);
155
156 auto BlockFreq = BFI.getBlockFreq(CB->getParent()).getFrequency();
157 auto EntryFreq = BFI.getEntryFreq().getFrequency();
158 uint64_t MinFreq =
159 static_cast<uint64_t>(EntryFreq * CoroElideBranchRatio);
160
161 if (BlockFreq < MinFreq) {
162 ORE.emit([&]() {
164 DEBUG_TYPE, "CoroAnnotationElideUnlikely", Caller)
165 << "'" << ore::NV("callee", Callee->getName())
166 << "' not elided in '"
167 << ore::NV("caller", Caller->getName())
168 << "' because of low frequency: "
169 << ore::NV("block_freq", BlockFreq)
170 << " (threshold: " << ore::NV("min_freq", MinFreq) << ")";
171 });
172 continue;
173 }
174
175 auto *CallerN = CG.lookup(*Caller);
176 auto *CallerC = CallerN ? CG.lookupSCC(*CallerN) : nullptr;
177 // If CallerC is nullptr, it means LazyCallGraph hasn't visited Caller
178 // yet. Skip the call graph update.
179 auto ShouldUpdateCallGraph = !!CallerC;
180 processCall(CB, Caller, NewCallee, FrameSize, FrameAlign);
181
182 ORE.emit([&]() {
183 return OptimizationRemark(DEBUG_TYPE, "CoroAnnotationElide", Caller)
184 << "'" << ore::NV("callee", Callee->getName())
185 << "' elided in '" << ore::NV("caller", Caller->getName())
186 << "' (block_freq: " << ore::NV("block_freq", BlockFreq)
187 << ")";
188 });
189
190 FAM.invalidate(*Caller, PreservedAnalyses::none());
191 Changed = true;
192 if (ShouldUpdateCallGraph)
193 updateCGAndAnalysisManagerForCGSCCPass(CG, *CallerC, *CallerN, AM, UR,
194 FAM);
195
196 } else {
197 ORE.emit([&]() {
198 return OptimizationRemarkMissed(DEBUG_TYPE, "CoroAnnotationElide",
199 Caller)
200 << "'" << ore::NV("callee", Callee->getName())
201 << "' not elided in '" << ore::NV("caller", Caller->getName())
202 << "' (caller_presplit="
203 << ore::NV("caller_presplit", IsCallerPresplitCoroutine)
204 << ", elide_safe_attr=" << ore::NV("elide_safe_attr", HasAttr)
205 << ")";
206 });
207 }
208 }
209 }
210
212}
unsigned uint64_t
MachineBasicBlock MachineBasicBlock::iterator DebugLoc DL
static GCRegistry::Add< ShadowStackGC > C("shadow-stack", "Very portable GC for uncooperative code generators")
This header provides classes for managing passes over SCCs of the call graph.
This file provides interfaces used to manipulate a call graph, regardless if it is a "old style" Call...
static void processCall(CallBase *CB, Function *Caller, Function *NewCallee, uint64_t FrameSize, Align FrameAlign)
static cl::opt< float > CoroElideBranchRatio("coro-elide-branch-ratio", cl::init(0.55), cl::Hidden, cl::desc("Minimum BranchProbability to consider a elide a coroutine."))
static Instruction * getFirstNonAllocaInTheEntryBlock(Function *F)
cl::opt< unsigned > MinBlockCounterExecution
static Value * allocateFrameInCaller(Function *Caller, uint64_t FrameSize, Align FrameAlign)
#define DEBUG_TYPE
Module.h This file contains the declarations for the Module class.
This header defines various interfaces for pass management in LLVM.
iv Induction Variable Users
Definition IVUsers.cpp:48
Implements a lazy call graph analysis and related passes for the new pass manager.
Legalize the Machine IR a function s Machine IR
Definition Legalizer.cpp:85
#define F(x, y, z)
Definition MD5.cpp:54
#define I(x, y, z)
Definition MD5.cpp:57
uint64_t IntrinsicInst * II
FunctionAnalysisManager FAM
static const unsigned FramePtr
an instruction to allocate memory on the stack
PassT::Result & getResult(IRUnitT &IR, ExtraArgTs... ExtraArgs)
Get the result of an analysis pass for a given IR unit.
static LLVM_ABI ArrayType * get(Type *ElementType, uint64_t NumElements)
This static method is the primary way to construct an ArrayType.
InstListType::iterator iterator
Instruction iterators...
Definition BasicBlock.h:170
Analysis pass which computes BlockFrequencyInfo.
Base class for all callable instructions (InvokeInst and CallInst) Holds everything related to callin...
void setCallingConv(CallingConv::ID CC)
bundle_op_iterator bundle_op_info_begin()
Return the start of the list of BundleOpInfo instances associated with this OperandBundleUser.
CallingConv::ID getCallingConv() const
bundle_op_iterator bundle_op_info_end()
Return the end of the list of BundleOpInfo instances associated with this OperandBundleUser.
User::op_iterator arg_begin()
Return the iterator pointing to the beginning of the argument list.
void setAttributes(AttributeList A)
Set the attributes for this call.
User::op_iterator arg_end()
Return the iterator pointing to the end of the argument list.
void removeFnAttr(Attribute::AttrKind Kind)
Removes the attribute from the function.
AttributeList getAttributes() const
Return the attributes for this call.
void setCalledFunction(Function *Fn)
Sets the function called, including updating the function type.
Wrapper to unify "old style" CallGraph and "new style" LazyCallGraph.
void initialize(LazyCallGraph &LCG, LazyCallGraph::SCC &SCC, CGSCCAnalysisManager &AM, CGSCCUpdateResult &UR)
Initializers for usage outside of a CGSCC pass, inside a CGSCC pass in the old and new pass manager (...
static CallInst * Create(FunctionType *Ty, Value *F, const Twine &NameStr="", InsertPosition InsertBefore=nullptr)
A parsed version of the target data layout string in and methods for querying it.
Definition DataLayout.h:64
A proxy from a FunctionAnalysisManager to an SCC.
FunctionType * getFunctionType() const
Returns the FunctionType for me.
Definition Function.h:211
uint64_t getParamDereferenceableBytes(unsigned ArgNo) const
Extract the number of dereferenceable bytes for a parameter.
Definition Function.h:498
MaybeAlign getParamAlign(unsigned ArgNo) const
Definition Function.h:463
size_t arg_size() const
Definition Function.h:885
This class captures the data input to the InlineFunction call, and records the auxiliary results prod...
Definition Cloning.h:259
InlineResult is basically true or false.
Definition InlineCost.h:181
const DebugLoc & getDebugLoc() const
Return the debug location for this node as a DebugLoc.
LLVM_ABI InstListType::iterator eraseFromParent()
This method unlinks 'this' from the containing basic block and deletes it.
void setDebugLoc(DebugLoc Loc)
Set the debug location information for this instruction.
static InvokeInst * Create(FunctionType *Ty, Value *Func, BasicBlock *IfNormal, BasicBlock *IfException, ArrayRef< Value * > Args, const Twine &NameStr, InsertPosition InsertBefore=nullptr)
This is an important class for using LLVM in a threaded context.
Definition LLVMContext.h:68
A node in the call graph.
An SCC of the call graph.
A lazily constructed view of the call graph of a module.
SCC * lookupSCC(Node &N) const
Lookup a function's SCC in the graph.
Node * lookup(const Function &F) const
Lookup a function in the graph which has already been scanned and added.
Diagnostic information for missed-optimization remarks.
Diagnostic information for applied optimization remarks.
A set of analyses that are preserved following a run of a transformation pass.
Definition Analysis.h:112
static PreservedAnalyses none()
Convenience factory function for the empty preserved set.
Definition Analysis.h:115
static PreservedAnalyses all()
Construct a special preserved set that preserves all passes.
Definition Analysis.h:118
void append(ItTy in_start, ItTy in_end)
Add the specified range to the end of the SmallVector.
void push_back(const T &Elt)
This is a 'vector' (really, a variable-sized array), optimized for the case when the array is small.
static LLVM_ABI IntegerType * getInt8Ty(LLVMContext &C)
Definition Type.cpp:307
LLVM Value Representation.
Definition Value.h:75
LLVM_ABI void replaceAllUsesWith(Value *V)
Change all uses of this to point to a new Value.
Definition Value.cpp:553
self_iterator getIterator()
Definition ilist_node.h:123
Changed
Pass manager infrastructure for declaring and invalidating analyses.
#define llvm_unreachable(msg)
Marks that the current location is not supposed to be reachable.
initializer< Ty > init(const Ty &Val)
DiagnosticInfoOptimizationBase::Argument NV
This is an optimization pass for GlobalISel generic memory operations.
decltype(auto) dyn_cast(const From &Val)
dyn_cast<X> - Return the argument parameter cast to the specified type.
Definition Casting.h:643
LLVM_ABI LazyCallGraph::SCC & updateCGAndAnalysisManagerForCGSCCPass(LazyCallGraph &G, LazyCallGraph::SCC &C, LazyCallGraph::Node &N, CGSCCAnalysisManager &AM, CGSCCUpdateResult &UR, FunctionAnalysisManager &FAM)
Helper to update the call graph after running a CGSCC pass.
LLVM_ABI InlineResult InlineFunction(CallBase &CB, InlineFunctionInfo &IFI, bool MergeAttributes=false, AAResults *CalleeAAR=nullptr, bool InsertLifetime=true, bool TrackInlineHistory=false, Function *ForwardVarArgsTo=nullptr, OptimizationRemarkEmitter *ORE=nullptr)
This function inlines the called function into the basic block of the caller.
AnalysisManager< LazyCallGraph::SCC, LazyCallGraph & > CGSCCAnalysisManager
The CGSCC analysis manager.
bool isa(const From &Val)
isa<X> - Return true if the parameter to the template is an instance of one of the template type argu...
Definition Casting.h:547
#define N
This struct is a compact representation of a valid (non-zero power of two) alignment.
Definition Alignment.h:39
Support structure for SCC passes to communicate updates the call graph back to the CGSCC pass manager...
LLVM_ABI PreservedAnalyses run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR)
Align valueOrOne() const
For convenience, returns a valid alignment or 1 if undefined.
Definition Alignment.h:130