1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-10-18 18:42:46 +02:00

[CallGraph] Add support for callback call sites

Summary:
This patch changes call graph analysis to recognize callback call sites
and add an artificial 'reference' call record from the broker function
caller to the callback function in the call graph. A presence of such
reference enforces bottom-up traversal order for callback functions in
CG SCC pass manager because callback function logically becomes a callee
of the broker function caller.

Reviewers: jdoerfert, hfinkel, sstefan1, baziotis

Reviewed By: jdoerfert

Subscribers: hiraditya, kuter, sstefan1, llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D82572
This commit is contained in:
Sergey Dmitriev 2020-06-29 16:11:16 -07:00
parent c8f56d9b25
commit 1b3aa514d1
12 changed files with 112 additions and 27 deletions

View File

@ -175,13 +175,21 @@ class CallGraphNode {
public:
/// A pair of the calling instruction (a call or invoke)
/// and the call graph node being called.
using CallRecord = std::pair<WeakTrackingVH, CallGraphNode *>;
/// Call graph node may have two types of call records which represent an edge
/// in the call graph - reference or a call edge. Reference edges are not
/// associated with any call instruction and are created with the first field
/// set to `None`, while real call edges have instruction address in this
/// field. Therefore, all real call edges are expected to have a value in the
/// first field and it is not supposed to be `nullptr`.
/// Reference edges, for example, are used for connecting broker function
/// caller to the callback function for callback call sites.
using CallRecord = std::pair<Optional<WeakTrackingVH>, CallGraphNode *>;
public:
using CalledFunctionsVector = std::vector<CallRecord>;
/// Creates a node for the specified function.
inline CallGraphNode(Function *F) : F(F) {}
inline CallGraphNode(CallGraph *CG, Function *F) : CG(CG), F(F) {}
CallGraphNode(const CallGraphNode &) = delete;
CallGraphNode &operator=(const CallGraphNode &) = delete;
@ -243,7 +251,8 @@ public:
assert(!Call || !Call->getCalledFunction() ||
!Call->getCalledFunction()->isIntrinsic() ||
!Intrinsic::isLeaf(Call->getCalledFunction()->getIntrinsicID()));
CalledFunctions.emplace_back(Call, M);
CalledFunctions.emplace_back(
Call ? Optional<WeakTrackingVH>(Call) : Optional<WeakTrackingVH>(), M);
M->AddRef();
}
@ -279,6 +288,7 @@ public:
private:
friend class CallGraph;
CallGraph *CG;
Function *F;
std::vector<CallRecord> CalledFunctions;

View File

@ -221,6 +221,27 @@ public:
}
};
/// Apply function Func to each CB's callback call site.
template <typename UnaryFunction>
void forEachCallbackCallSite(const CallBase &CB, UnaryFunction Func) {
SmallVector<const Use *, 4u> CallbackUses;
AbstractCallSite::getCallbackUses(CB, CallbackUses);
for (const Use *U : CallbackUses) {
AbstractCallSite ACS(U);
assert(ACS && ACS.isCallbackCall() && "must be a callback call");
Func(ACS);
}
}
/// Apply function Func to each CB's callback function.
template <typename UnaryFunction>
void forEachCallbackFunction(const CallBase &CB, UnaryFunction Func) {
forEachCallbackCallSite(CB, [&Func](AbstractCallSite &ACS) {
if (Function *Callback = ACS.getCalledFunction())
Func(Callback);
});
}
} // end namespace llvm
#endif // LLVM_IR_ABSTRACTCALLSITE_H

View File

@ -10,6 +10,7 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/IR/AbstractCallSite.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Intrinsics.h"
@ -31,7 +32,7 @@ using namespace llvm;
CallGraph::CallGraph(Module &M)
: M(M), ExternalCallingNode(getOrInsertFunction(nullptr)),
CallsExternalNode(std::make_unique<CallGraphNode>(nullptr)) {
CallsExternalNode(std::make_unique<CallGraphNode>(this, nullptr)) {
// Add every interesting function to the call graph.
for (Function &F : M)
if (!isDbgInfoIntrinsic(F.getIntrinsicID()))
@ -44,6 +45,11 @@ CallGraph::CallGraph(CallGraph &&Arg)
CallsExternalNode(std::move(Arg.CallsExternalNode)) {
Arg.FunctionMap.clear();
Arg.ExternalCallingNode = nullptr;
// Update parent CG for all call graph's nodes.
CallsExternalNode->CG = this;
for (auto &P : FunctionMap)
P.second->CG = this;
}
CallGraph::~CallGraph() {
@ -99,6 +105,11 @@ void CallGraph::populateCallGraphNode(CallGraphNode *Node) {
Node->addCalledFunction(Call, CallsExternalNode.get());
else if (!Callee->isIntrinsic())
Node->addCalledFunction(Call, getOrInsertFunction(Callee));
// Add reference to callback functions.
forEachCallbackFunction(*Call, [=](Function *CB) {
Node->addCalledFunction(nullptr, getOrInsertFunction(CB));
});
}
}
}
@ -178,7 +189,7 @@ CallGraphNode *CallGraph::getOrInsertFunction(const Function *F) {
return CGN.get();
assert((!F || F->getParent() == &M) && "Function not in current module!");
CGN = std::make_unique<CallGraphNode>(const_cast<Function *>(F));
CGN = std::make_unique<CallGraphNode>(this, const_cast<Function *>(F));
return CGN.get();
}
@ -214,10 +225,15 @@ LLVM_DUMP_METHOD void CallGraphNode::dump() const { print(dbgs()); }
void CallGraphNode::removeCallEdgeFor(CallBase &Call) {
for (CalledFunctionsVector::iterator I = CalledFunctions.begin(); ; ++I) {
assert(I != CalledFunctions.end() && "Cannot find callsite to remove!");
if (I->first == &Call) {
if (I->first && *I->first == &Call) {
I->second->DropRef();
*I = CalledFunctions.back();
CalledFunctions.pop_back();
// Remove all references to callback functions if there are any.
forEachCallbackFunction(Call, [=](Function *CB) {
removeOneAbstractEdgeTo(CG->getOrInsertFunction(CB));
});
return;
}
}
@ -242,7 +258,7 @@ void CallGraphNode::removeOneAbstractEdgeTo(CallGraphNode *Callee) {
for (CalledFunctionsVector::iterator I = CalledFunctions.begin(); ; ++I) {
assert(I != CalledFunctions.end() && "Cannot find callee to remove!");
CallRecord &CR = *I;
if (CR.second == Callee && CR.first == nullptr) {
if (CR.second == Callee && !CR.first) {
Callee->DropRef();
*I = CalledFunctions.back();
CalledFunctions.pop_back();
@ -258,11 +274,19 @@ void CallGraphNode::replaceCallEdge(CallBase &Call, CallBase &NewCall,
CallGraphNode *NewNode) {
for (CalledFunctionsVector::iterator I = CalledFunctions.begin(); ; ++I) {
assert(I != CalledFunctions.end() && "Cannot find callsite to remove!");
if (I->first == &Call) {
if (I->first && *I->first == &Call) {
I->second->DropRef();
I->first = &NewCall;
I->second = NewNode;
NewNode->AddRef();
// Refresh callback references.
forEachCallbackFunction(Call, [=](Function *CB) {
removeOneAbstractEdgeTo(CG->getOrInsertFunction(CB));
});
forEachCallbackFunction(NewCall, [=](Function *CB) {
addCalledFunction(nullptr, CG->getOrInsertFunction(CB));
});
return;
}
}

View File

@ -227,9 +227,15 @@ bool CGPassManager::RefreshCallGraph(const CallGraphSCC &CurSCC, CallGraph &CG,
// Get the set of call sites currently in the function.
for (CallGraphNode::iterator I = CGN->begin(), E = CGN->end(); I != E; ) {
// Skip "reference" call records that do not have call instruction.
if (!I->first) {
++I;
continue;
}
// If this call site is null, then the function pass deleted the call
// entirely and the WeakTrackingVH nulled it out.
auto *Call = dyn_cast_or_null<CallBase>(I->first);
auto *Call = dyn_cast_or_null<CallBase>(*I->first);
if (!Call ||
// If we've already seen this call site, then the FunctionPass RAUW'd
// one call with another, which resulted in two "uses" in the edge

View File

@ -109,7 +109,7 @@ PreservedAnalyses SyntheticCountsPropagation::run(Module &M,
Optional<Scaled64> Res = None;
if (!Edge.first)
return Res;
CallBase &CB = *cast<CallBase>(Edge.first);
CallBase &CB = *cast<CallBase>(*Edge.first);
Function *Caller = CB.getCaller();
auto &BFI = FAM.getResult<BlockFrequencyAnalysis>(*Caller);

View File

@ -149,7 +149,7 @@ bool CallGraphUpdater::replaceCallSite(CallBase &OldCS, CallBase &NewCS) {
CG->getOrInsertFunction(NewCS.getCalledFunction());
CallGraphNode *CallerNode = (*CG)[Caller];
if (llvm::none_of(*CallerNode, [&OldCS](const CallGraphNode::CallRecord &CR) {
return CR.first == &OldCS;
return CR.first && *CR.first == &OldCS;
}))
return false;
CallerNode->replaceCallEdge(OldCS, NewCS, NewCalleeNode);

View File

@ -1291,7 +1291,11 @@ static void UpdateCallGraphAfterInlining(CallBase &CB,
}
for (; I != E; ++I) {
const Value *OrigCall = I->first;
// Skip 'refererence' call records.
if (!I->first)
continue;
const Value *OrigCall = *I->first;
ValueToValueMapTy::iterator VMI = VMap.find(OrigCall);
// Only copy the edge if the call was inlined!

View File

@ -0,0 +1,20 @@
; RUN: opt < %s -print-callgraph -disable-output 2>&1 | FileCheck %s
; RUN: opt < %s -passes=print-callgraph -disable-output 2>&1 | FileCheck %s
; CHECK: Call graph node for function: 'caller'
; CHECK-NEXT: CS<{{.*}}> calls function 'broker'
; CHECK-NEXT: CS<None> calls function 'callback'
define void @caller(i32* %arg) {
call void @broker(void (i32*)* @callback, i32* %arg)
ret void
}
define void @callback(i32* %arg) {
ret void
}
declare !callback !0 void @broker(void (i32*)*, i32*)
!0 = !{!1}
!1 = !{i64 0, i64 1, i1 false}

View File

@ -23,7 +23,7 @@ entry:
; CHECK: Call graph node <<null function>>
; CHECK: CS<0x0> calls function 'f'
; CHECK: CS<None> calls function 'f'
; CHECK: Call graph node for function: 'calls_patchpoint'
; CHECK-NEXT: CS<[[addr_1:[^>]+]]> calls external node

View File

@ -58,7 +58,7 @@ define dso_local void @foo(i32 %N) {
; IS__CGSCC_OPM-NEXT: store i32 [[N]], i32* [[N_ADDR]], align 4
; IS__CGSCC_OPM-NEXT: store float 3.000000e+00, float* [[P]], align 4
; IS__CGSCC_OPM-NEXT: store i32 7, i32* [[N_ADDR]], align 4
; IS__CGSCC_OPM-NEXT: call void (%struct.ident_t*, i32, void (i32*, i32*, ...)*, ...) @__kmpc_fork_call(%struct.ident_t* nonnull align 8 dereferenceable(24) @1, i32 3, void (i32*, i32*, ...)* bitcast (void (i32*, i32*, i32*, float*, i64)* @.omp_outlined. to void (i32*, i32*, ...)*), i32* nonnull align 4 dereferenceable(4) [[N_ADDR]], float* nonnull align 4 dereferenceable(4) [[P]], i64 4617315517961601024)
; IS__CGSCC_OPM-NEXT: call void (%struct.ident_t*, i32, void (i32*, i32*, ...)*, ...) @__kmpc_fork_call(%struct.ident_t* nonnull align 8 dereferenceable(24) @1, i32 3, void (i32*, i32*, ...)* bitcast (void (i32*, i32*, i32*, float*, i64)* @.omp_outlined. to void (i32*, i32*, ...)*), i32* nocapture nonnull readonly align 4 dereferenceable(4) [[N_ADDR]], float* nocapture nonnull readonly align 4 dereferenceable(4) [[P]], i64 4617315517961601024)
; IS__CGSCC_OPM-NEXT: ret void
;
; IS__CGSCC_NPM-LABEL: define {{[^@]+}}@foo

View File

@ -47,10 +47,10 @@ define dso_local i32 @main() {
; IS__CGSCC_OPM-NEXT: [[ALLOC1:%.*]] = alloca i8, align 8
; IS__CGSCC_OPM-NEXT: [[ALLOC2:%.*]] = alloca i8, align 8
; IS__CGSCC_OPM-NEXT: [[THREAD:%.*]] = alloca i64, align 8
; IS__CGSCC_OPM-NEXT: [[CALL:%.*]] = call i32 @pthread_create(i64* nonnull align 8 dereferenceable(8) [[THREAD]], %union.pthread_attr_t* noalias nocapture align 536870912 null, i8* (i8*)* nonnull @foo, i8* noalias nocapture align 536870912 null)
; IS__CGSCC_OPM-NEXT: [[CALL1:%.*]] = call i32 @pthread_create(i64* nonnull align 8 dereferenceable(8) [[THREAD]], %union.pthread_attr_t* noalias nocapture align 536870912 null, i8* (i8*)* nonnull @bar, i8* nonnull align 8 dereferenceable(8) bitcast (i8** @GlobalVPtr to i8*))
; IS__CGSCC_OPM-NEXT: [[CALL2:%.*]] = call i32 @pthread_create(i64* nonnull align 8 dereferenceable(8) [[THREAD]], %union.pthread_attr_t* noalias nocapture align 536870912 null, i8* (i8*)* nonnull @baz, i8* nocapture nonnull align 8 dereferenceable(1) [[ALLOC1]])
; IS__CGSCC_OPM-NEXT: [[CALL3:%.*]] = call i32 @pthread_create(i64* nonnull align 8 dereferenceable(8) [[THREAD]], %union.pthread_attr_t* noalias nocapture align 536870912 null, i8* (i8*)* nonnull @buz, i8* nonnull align 8 dereferenceable(1) [[ALLOC2]])
; IS__CGSCC_OPM-NEXT: [[CALL:%.*]] = call i32 @pthread_create(i64* nonnull align 8 dereferenceable(8) [[THREAD]], %union.pthread_attr_t* noalias nocapture align 536870912 null, i8* (i8*)* nonnull @foo, i8* noalias nocapture nofree readnone align 536870912 null)
; IS__CGSCC_OPM-NEXT: [[CALL1:%.*]] = call i32 @pthread_create(i64* nonnull align 8 dereferenceable(8) [[THREAD]], %union.pthread_attr_t* noalias nocapture align 536870912 null, i8* (i8*)* nonnull @bar, i8* noalias nofree nonnull readnone align 8 dereferenceable(8) bitcast (i8** @GlobalVPtr to i8*))
; IS__CGSCC_OPM-NEXT: [[CALL2:%.*]] = call i32 @pthread_create(i64* nonnull align 8 dereferenceable(8) [[THREAD]], %union.pthread_attr_t* noalias nocapture align 536870912 null, i8* (i8*)* nonnull @baz, i8* noalias nocapture nofree nonnull readnone align 8 dereferenceable(1) [[ALLOC1]])
; IS__CGSCC_OPM-NEXT: [[CALL3:%.*]] = call i32 @pthread_create(i64* nonnull align 8 dereferenceable(8) [[THREAD]], %union.pthread_attr_t* noalias nocapture align 536870912 null, i8* (i8*)* nonnull @buz, i8* noalias nofree nonnull readnone align 8 dereferenceable(1) [[ALLOC2]])
; IS__CGSCC_OPM-NEXT: ret i32 0
;
; IS__CGSCC_NPM-LABEL: define {{[^@]+}}@main()
@ -94,7 +94,7 @@ define internal i8* @bar(i8* %arg) {
; IS__TUNIT____-NEXT: ret i8* bitcast (i8** @GlobalVPtr to i8*)
;
; IS__CGSCC_OPM-LABEL: define {{[^@]+}}@bar
; IS__CGSCC_OPM-SAME: (i8* nofree nonnull readnone returned align 8 dereferenceable(8) "no-capture-maybe-returned" [[ARG:%.*]])
; IS__CGSCC_OPM-SAME: (i8* nofree readnone returned "no-capture-maybe-returned" [[ARG:%.*]])
; IS__CGSCC_OPM-NEXT: entry:
; IS__CGSCC_OPM-NEXT: ret i8* bitcast (i8** @GlobalVPtr to i8*)
;

View File

@ -49,7 +49,7 @@ define void @t0_caller(i32* %a) {
; IS__CGSCC_OPM-NEXT: [[TMP0:%.*]] = bitcast i32* [[B]] to i8*
; IS__CGSCC_OPM-NEXT: store i32 42, i32* [[B]], align 32
; IS__CGSCC_OPM-NEXT: store i32* [[B]], i32** [[C]], align 64
; IS__CGSCC_OPM-NEXT: call void (i32*, i32*, void (i32*, i32*, ...)*, ...) @t0_callback_broker(i32* noalias nocapture align 536870912 null, i32* nonnull align 128 dereferenceable(4) [[PTR]], void (i32*, i32*, ...)* bitcast (void (i32*, i32*, i32*, i64, i32**)* @t0_callback_callee to void (i32*, i32*, ...)*), i32* align 256 [[A]], i64 99, i32** nonnull align 64 dereferenceable(8) [[C]])
; IS__CGSCC_OPM-NEXT: call void (i32*, i32*, void (i32*, i32*, ...)*, ...) @t0_callback_broker(i32* noalias nocapture align 536870912 null, i32* nonnull align 128 dereferenceable(4) [[PTR]], void (i32*, i32*, ...)* bitcast (void (i32*, i32*, i32*, i64, i32**)* @t0_callback_callee to void (i32*, i32*, ...)*), i32* align 256 [[A]], i64 99, i32** nocapture nonnull readonly align 64 dereferenceable(8) [[C]])
; IS__CGSCC_OPM-NEXT: ret void
;
; IS__CGSCC_NPM-LABEL: define {{[^@]+}}@t0_caller
@ -140,7 +140,7 @@ define void @t1_caller(i32* noalias %a) {
; IS__TUNIT_NPM-NEXT: ret void
;
; IS__CGSCC_OPM-LABEL: define {{[^@]+}}@t1_caller
; IS__CGSCC_OPM-SAME: (i32* noalias align 256 [[A:%.*]])
; IS__CGSCC_OPM-SAME: (i32* noalias nocapture align 256 [[A:%.*]])
; IS__CGSCC_OPM-NEXT: entry:
; IS__CGSCC_OPM-NEXT: [[B:%.*]] = alloca i32, align 32
; IS__CGSCC_OPM-NEXT: [[C:%.*]] = alloca i32*, align 64
@ -148,7 +148,7 @@ define void @t1_caller(i32* noalias %a) {
; IS__CGSCC_OPM-NEXT: [[TMP0:%.*]] = bitcast i32* [[B]] to i8*
; IS__CGSCC_OPM-NEXT: store i32 42, i32* [[B]], align 32
; IS__CGSCC_OPM-NEXT: store i32* [[B]], i32** [[C]], align 64
; IS__CGSCC_OPM-NEXT: call void (i32*, i32*, void (i32*, i32*, ...)*, ...) @t1_callback_broker(i32* noalias nocapture align 536870912 null, i32* nocapture nonnull align 128 dereferenceable(4) [[PTR]], void (i32*, i32*, ...)* nocapture bitcast (void (i32*, i32*, i32*, i64, i32**)* @t1_callback_callee to void (i32*, i32*, ...)*), i32* align 256 [[A]], i64 99, i32** nonnull align 64 dereferenceable(8) [[C]])
; IS__CGSCC_OPM-NEXT: call void (i32*, i32*, void (i32*, i32*, ...)*, ...) @t1_callback_broker(i32* noalias nocapture align 536870912 null, i32* nocapture nonnull align 128 dereferenceable(4) [[PTR]], void (i32*, i32*, ...)* nocapture bitcast (void (i32*, i32*, i32*, i64, i32**)* @t1_callback_callee to void (i32*, i32*, ...)*), i32* nocapture align 256 [[A]], i64 99, i32** nocapture nonnull readonly align 64 dereferenceable(8) [[C]])
; IS__CGSCC_OPM-NEXT: ret void
;
; IS__CGSCC_NPM-LABEL: define {{[^@]+}}@t1_caller
@ -238,7 +238,7 @@ define void @t2_caller(i32* noalias %a) {
; IS__TUNIT_NPM-NEXT: ret void
;
; IS__CGSCC_OPM-LABEL: define {{[^@]+}}@t2_caller
; IS__CGSCC_OPM-SAME: (i32* noalias align 256 [[A:%.*]])
; IS__CGSCC_OPM-SAME: (i32* noalias nocapture align 256 [[A:%.*]])
; IS__CGSCC_OPM-NEXT: entry:
; IS__CGSCC_OPM-NEXT: [[B:%.*]] = alloca i32, align 32
; IS__CGSCC_OPM-NEXT: [[C:%.*]] = alloca i32*, align 64
@ -246,7 +246,7 @@ define void @t2_caller(i32* noalias %a) {
; IS__CGSCC_OPM-NEXT: [[TMP0:%.*]] = bitcast i32* [[B]] to i8*
; IS__CGSCC_OPM-NEXT: store i32 42, i32* [[B]], align 32
; IS__CGSCC_OPM-NEXT: store i32* [[B]], i32** [[C]], align 64
; IS__CGSCC_OPM-NEXT: call void (i32*, i32*, void (i32*, i32*, ...)*, ...) @t2_callback_broker(i32* noalias nocapture align 536870912 null, i32* nocapture nonnull align 128 dereferenceable(4) [[PTR]], void (i32*, i32*, ...)* nocapture bitcast (void (i32*, i32*, i32*, i64, i32**)* @t2_callback_callee to void (i32*, i32*, ...)*), i32* align 256 [[A]], i64 99, i32** nonnull align 64 dereferenceable(8) [[C]])
; IS__CGSCC_OPM-NEXT: call void (i32*, i32*, void (i32*, i32*, ...)*, ...) @t2_callback_broker(i32* noalias nocapture align 536870912 null, i32* nocapture nonnull align 128 dereferenceable(4) [[PTR]], void (i32*, i32*, ...)* nocapture bitcast (void (i32*, i32*, i32*, i64, i32**)* @t2_callback_callee to void (i32*, i32*, ...)*), i32* nocapture align 256 [[A]], i64 99, i32** nocapture nonnull readonly align 64 dereferenceable(8) [[C]])
; IS__CGSCC_OPM-NEXT: ret void
;
; IS__CGSCC_NPM-LABEL: define {{[^@]+}}@t2_caller
@ -340,7 +340,7 @@ define void @t3_caller(i32* noalias %a) {
; IS__TUNIT_NPM-NEXT: ret void
;
; IS__CGSCC_OPM-LABEL: define {{[^@]+}}@t3_caller
; IS__CGSCC_OPM-SAME: (i32* noalias align 256 [[A:%.*]])
; IS__CGSCC_OPM-SAME: (i32* noalias nocapture align 256 [[A:%.*]])
; IS__CGSCC_OPM-NEXT: entry:
; IS__CGSCC_OPM-NEXT: [[B:%.*]] = alloca i32, align 32
; IS__CGSCC_OPM-NEXT: [[C:%.*]] = alloca i32*, align 64
@ -348,8 +348,8 @@ define void @t3_caller(i32* noalias %a) {
; IS__CGSCC_OPM-NEXT: [[TMP0:%.*]] = bitcast i32* [[B]] to i8*
; IS__CGSCC_OPM-NEXT: store i32 42, i32* [[B]], align 32
; IS__CGSCC_OPM-NEXT: store i32* [[B]], i32** [[C]], align 64
; IS__CGSCC_OPM-NEXT: call void (i32*, i32*, void (i32*, i32*, ...)*, ...) @t3_callback_broker(i32* noalias nocapture align 536870912 null, i32* nocapture nonnull align 128 dereferenceable(4) [[PTR]], void (i32*, i32*, ...)* nocapture bitcast (void (i32*, i32*, i32*, i64, i32**)* @t3_callback_callee to void (i32*, i32*, ...)*), i32* align 256 [[A]], i64 99, i32** nonnull align 64 dereferenceable(8) [[C]])
; IS__CGSCC_OPM-NEXT: call void (i32*, i32*, void (i32*, i32*, ...)*, ...) @t3_callback_broker(i32* noalias nocapture align 536870912 null, i32* nocapture nonnull align 128 dereferenceable(4) [[PTR]], void (i32*, i32*, ...)* nocapture bitcast (void (i32*, i32*, i32*, i64, i32**)* @t3_callback_callee to void (i32*, i32*, ...)*), i32* align 256 [[A]], i64 99, i32** nonnull align 64 dereferenceable(8) [[C]])
; IS__CGSCC_OPM-NEXT: call void (i32*, i32*, void (i32*, i32*, ...)*, ...) @t3_callback_broker(i32* noalias nocapture align 536870912 null, i32* nocapture nonnull align 128 dereferenceable(4) [[PTR]], void (i32*, i32*, ...)* nocapture bitcast (void (i32*, i32*, i32*, i64, i32**)* @t3_callback_callee to void (i32*, i32*, ...)*), i32* nocapture align 256 [[A]], i64 99, i32** nocapture nonnull readonly align 64 dereferenceable(8) [[C]])
; IS__CGSCC_OPM-NEXT: call void (i32*, i32*, void (i32*, i32*, ...)*, ...) @t3_callback_broker(i32* noalias nocapture align 536870912 null, i32* nocapture nonnull align 128 dereferenceable(4) [[PTR]], void (i32*, i32*, ...)* nocapture bitcast (void (i32*, i32*, i32*, i64, i32**)* @t3_callback_callee to void (i32*, i32*, ...)*), i32* nocapture align 256 [[A]], i64 99, i32** nocapture nonnull readonly align 64 dereferenceable(8) [[C]])
; IS__CGSCC_OPM-NEXT: ret void
;
; IS__CGSCC_NPM-LABEL: define {{[^@]+}}@t3_caller