Inter-procedural CAT Simone Campanoni simonec@eecs.northwestern.edu
Procedures/functions void bar (void){ bar x = 5; • Abstraction px = &x; foo • Cornerstone of programming foo(px); • Introduces barriers to analysis Is x constant? y = x + 5; } • So far looked at intra-procedural analysis • Analyzing a single procedure • Inter-procedural analysis uses calling relationships among procedures (Call Graph) • Enables more precise analysis information
Inter-procedural analysis Goal : Avoid making overly conservative assumptions about the effects of procedures and the state at call sites Terminology int a, e; // Globals void foo(int *b, int *c){ // Formal parameters (*b) = e; } bar(){ int d; // Local variables foo(a, d); // Actual parameters }
Inter-procedural analysis vs. inter-procedural transformation Inter-procedural analysis • Gather information across multiple procedures (up to the entire program) • Can use this information to improve intra-procedural analyses and transformation (e.g., CP) Inter-procedural transformation • Code transformations that involve multiple procedures e.g., Inlining, procedure cloning, function specialization
Outline ①Sensitivity of analysis ②Single compilation ③Separate compilations ④Caller -> callee vs. callee -> caller propagations ⑤Final remarks
Sensitivity of intra-procedural analysis • Flow-sensitive vs. flow-insensitive A B C A B C
Flow sensitivity example Flow-sensitive analysis Is x constant? • Computes one answer for every program point void f (int x){ • x is 4 after A A: x = 4; • x is 5 after B … • Requires iterative data-flow analysis or similar technique B: x = 5; Flow-insensitive analysis } • Computes one answer for the entire procedure • x is not constant • Can compute in linear time • Less accurate (ignores control flows)
Sensitivity of intra-procedural analysis • Flow-sensitive vs. flow-insensitive A B C A B C • Path-sensitive vs. path-insensitive
Path sensitivity example Path-sensitive analysis Is x constant? if (x == 0) • Computes one answer for every execution path • x is 4 at print(x) if you came from the left path • x is 5 at print(x) if you came from the right path x = 4; x = 5; • Subsumes flow-sensitivity • Very expensive print(x) Path-insensitive analysis • Computes one answer for all path • x is not constant at print(x)
Sensitivity of inter-procedural analysis • Flow-sensitive vs. flow-insensitive A B C A B C • Path-sensitive vs. path-insensitive • Context-sensitive vs. context-insensitive
Context sensitivity example Context-sensitive analysis Is x constant? • Computes one answer for every call-site a = id(4); b = id(5); • x is 4 in the first call • x is 5 in the second call • Re-analyzes callee for each caller id (x) { return x;} Context-insensitive analysis • Computes one answer for all call-sites: • x is not constant • Perform one analysis independent of callers • Suffers from unrealizable paths: • Can mistakenly conclude that id(4) can return 5 because we merge information from all call-sites
Call graph • First problem: how do we know void foo (int a, int (*p_to_f)(int v)){ what procedures are called from where? int l = (*p_to_f)(5); a = l + 1; • Especially difficult in higher-order languages, return a; languages where functions are values } • What about C programs? • We’ll ignore this for now • Let’s assume we have a (static) call graph • Indicates which procedures can call which other procedures, and from which program points
Call graph example From now on we assume we have a static call graph
Generating a call graph with LLVM • From the command line: opt -dot-callgraph program.bc -disable-output (see test0) • From your pass: • Explicit iteration • LLVM_callgraph/llvm/[0-3] • CallGraphWrappingPass • LLVM_callgraph/llvm/[4-6]
Generating a call graph with LLVM • From the command line: opt -dot-callgraph program.bc -disable-output (see test0) • From your pass: • Explicit iteration • LLVM_5/llvm/[0-3] • CallGraphWrappingPass • LLVM_5/llvm/[4-6]
DEMO
Using CallGraphWrappingPass • Declaring your pass dependence • Fetching the call graph
Using CallGraphWrappingPass • From a Function to a node of the call graph • From node to callees • From node to Function
DEMO
Outline ①Sensitivity of analysis ②Single compilation ③Separate compilations ④Caller -> callee vs. callee -> caller propagations ⑤Final remarks
Intra-procedural dataflow analysis • How did we do previously? main() { A: x = 7; main p B: r = p(x); IN = { } C: y = 80; A F D: t = p(y); IN = {A} E: print t, r; B IN = { } IN = { } } IN = {A, B} int p (int v) { C G H IN = {A, B, C} F: if (v < 10) G: m = 1; D IN = {G, H} else IN = {A, B, C, D } H: m = 2; I E I: return m; }
Inter-procedural dataflow analysis flow-sensitive • Accuracy? • How can we handle procedure calls? • Performance? • Obvious idea: make one big CFG ( control-flow supergraph ) • No separate analysis main() { A: x = 7; B: r = p(x); C: y = 80; IN = {A, G, H, C} A F D: t = p(y); IN = {A} E: print t, r; B IN = {A,G,H,C} IN = {A,G,H,C} } IN = {A, G, H, C} int p (int v) { C G H IN = {A, G, H, C} F: if (v < 10) G: m = 1; D IN = {A, G, H, C } else IN = {A, G, H, C} H: m = 2; I E I: return m; }
Inter-procedural dataflow analysis flow-sensitive • Accuracy? • Problem of invalid paths: dataflow facts from one call site • How can we handle procedure calls? • Performance? “tainting” results at other call site • Obvious idea: make one big CFG ( control-flow supergraph ) • No separate analysis • p analyzed with merge of dataflow facts from all call sites main() { A: x = 7; • How to address this problem? B: r = p(x); C: y = 80; IN = {A, G, H, C} A F D: t = p(y); IN = {A} E: print t, r; B IN = {A,G,H,C} IN = {A,G,H,C} } IN = {A, G, H, C} int p (int v) { C G H IN = {A, G, H, C} F: if (v < 10) G: m = 1; D IN = {A, G, H, C } else IN = {A, G, H, C} H: m = 2; I E I: return m; }
Inter-procedural dataflow analysis flow/context-sensitive • Accuracy? main() { • Performance? A: x = 7; B: r = p(x); C: y = 80; IN = {B:{A},D:{A,C,G,H}} A F D: t = p(y); IN = {A} E: print t, r; B IN = {B:{A}, IN={B:{A}, D:{A,C,G,H}} D:{A,C,G,H}} } IN = {A, B:{G, H}} int p (int v) { C G H IN = {A, C, B:{G, H}} F: if (v < 10) IN = {B:{A,G,H}, G: m = 1; D D:{A,C,G,H}} else IN = {A, C, B:{G,H}, D:{G,H}} H: m = 2; I E I: return m; }
• Even an inter-procedural flow- and context- sensitive analysis isn’t able to perform the constant propagation we want • We need to make our analysis even more complex • Since this seems hard, let’s try something easier • Let’s try to follow a simpler solution: • We copy the body of the callee inside the caller • Function inlining
Inter-procedural code transformation: function inlining • Function inlining: • Use a new copy of a procedure’s CFG at a call site • Adjust copied code within the caller (e.g., rename variables, map formal parameters to actual parameters) • In LLVM: • You don’t need to implement this transformation, it already exists J • InlineResult InlineFunction(CallBase *, InlineFunctionInfo &, … ) Extra parameters are optional InlineFunctionInfo IFI; #include "llvm/Transforms/Utils/Cloning.h" if (InlineFunction(call, IFI)) { … } else { … }
Function inlining in LLVM and alias analysis • InlineResult InlineFunction( CallBase *, InlineFunctionInfo &, AAResults *CalleeAAR = nullptr, bool InsertLifetime = true ) void f (){ … // pre_g void f (){ call g() %1 = alloca() … // pre_g New … // post_g … // g_body live range of %1 Function inlining } … // post_g void g(){ } But we know %1 can only %1 = alloca(…) be used (directly or indirectly) … // g_body within g_body }
Inter-procedural code transformation: function inlining main p main() { A A: x = 7; B: r = p(x); F C: y = 80; B D: t = p(y); E: print t, r; } C G H int p (int v) { F: if (v < 10) D G: m = 1; else I H: m = 2; E I: return m; Example of function inlining: inline the callee of B }
Inter-procedural code transformation: function inlining main p main() { A A: x = 7; F’ B: r = p(x); F C: y = 80; G’ H’ D: t = p(y); I’ E: print t, r; } C G H int p (int v) { F: if (v < 10) D G: m = 1; else I H: m = 2; E I: return m; Another example of function inlining: inline the callee of D }
Inter-procedural code transformation: function inlining main p main() { A A: x = 7; F’ B: r = p(x); F C: y = 80; G’ H’ D: t = p(y); I’ E: print t, r; } C G H int p (int v) { F’’ F: if (v < 10) G’’ H’’ G: m = 1; I’’ else I H: m = 2; E I: return m; }
Recommend
More recommend