 Welcome to part 5 of the lecture on machine independent optimizations, today we will continue our discussion on control flow analysis. So, we defined dominators in the last part, so and we were discussing the natural loop structure which is defined by a back edge. So, just to do a bit of recap edges whose heads dominate their tails are called back edges. So, if there is an edge from a to b, b is the head and a is the tail. So, given a back edge n to d the natural loop of the edge is the node d plus the set of nodes that can reach n without going through d again. So, the property of the header is that you know of course, the rather the head is that d is the header of the loop. So, it is a single entry point to the loop that dominates all the nodes in the loop and at least one path back to the header exists, so that the loop can be iterated. Let us now consider the algorithm to find the natural loop structure based on this definition, this is a fairly straight forward algorithm it uses a stack. So, let us say the back edge under consideration is n to d to begin with the stack has been initialized to empty and the set of nodes in the loop has been initialized to d because that is the header. Once we initialize the loop to d it ensures us that we do not look at the predecessors of d which is you know we should not be doing that otherwise we would be going outside the loop. It calls the function insert n which just checks whether the node that is passed as a parameter is in the loop set. So, if it is not in the loop set it is added to the loop set and then it is pushed on to the stack. The reason we do this is to trace the predecessors of each of the nodes which are on the stack. So, now we begin so to begin with we have inserted n. So, that has also been added to the loop and it has been pushed on the stack. So, to begin with we have only n on the stack. So, while stack is not empty do pop it and for each predecessors of m do insert p. So, we go on doing this until the stack becomes empty. So, let us understand this algorithm with an example. So, here we have many back edges. So, the back edges are 7 to 4, 10 to 7, 4 to 3, 10 to 3 and 11 to 1. So, let us just take one of the back edges and understand how the loop can be computed. So, let us say we take the back edge from 7 to 4 why is it a back edge that is because 4 dominates 7 that can be easily checked here 4 dominates 7. In the algorithm we put 4 into the loop which is the header of this loop corresponding to the back edge 7 to 4. So, there is always a back edge to which a natural loop corresponds. So, we are considering tracing the loop structure of 7 to 4. So, we add 4 to it and then we do an insert on 7 which is also added to the loop and it is also pushed on to the stack. So, inside the while loop which keeps popping nodes from the stack we first pop 7. So, then we look at the predecessors of 7. So, these are the 2 predecessors of 7. So, let us say we add we go to 5. So, 5 is not in the loop. So, we add 5 to the loop structure and we also push 5 on to the stack. Then we look at 6 we also push 6 on to the stack and we add it to the loop. But once we reach pop 5 and 5 then we find that it is already added to the loop structure. So, nothing is done nothing is done for 6 as well. But then from 7 5 and 6 are not the only predecessors we also have another predecessors which is 10. So, we actually add 10 also to the loop and it is pushed on to the stack and once it is popped its predecessors is 8. So, 8 is put into the loop and again pushed on to the stack, but nothing more can be added from 8 because 7 and 8 are already in the loop. So, we get 4 5 6 7 8 10 as the loop structure of the back edge 7 to 4. So, 4 5 6 7 8 10. So, this is the loop structure of the back edge 7 to 4. So, let us consider the back edge 10 to 7 and see what happens. So, we add a 7 to the loop we add 10 to the loop and also push it on to the stack when we pop 10 its predecessor is 8. So, we add that to the loop and push it on to the stack and for 8 there are no other nodes to be added because 8 itself has been added to the loop. So, 7 and 8 and 10 happen to be the happen to be the nodes in the loop corresponding to 10 to 7. So far it is ok, but the most unintuitive result is for the loop from 4 to 3. So, we start with 3 added to the loop then we add 4 to the loop structure and once we add 4 the only predecessor of 4 is 7 in this case. So, we add 7 6 and 5 to the loop structure the predecessor of 7 is 10. So, we add 10 and 8 also to the loop structure. So, for this tiny you know seemingly single loop we have added 3 4 5 6 7 8 and 10. The reason why this is correct even though it looks unintuitive is once we start from 3 we can not only go to 4 and go back to 3 we can actually go to 4 then 5 then 7 then we try we can traverse 7 to 4 and then 4 to 3. Remember that to reach the node number 3 we will have to traverse the back edge if we do not do that then this must be the only way we reach 3 otherwise it is incorrect. So, for example, we can go to 4 5 7 8 and 10, but then we can also reach 3 via the other back edge, but that is not considered as a part of this loop for reaching 3 for the loop corresponding to the back edge 4 to 3 we must always traverse the back edge 4 to 3. So, we go down to 10 then we go to 7 then we go to 4 and then to 3. So, this is how the entire loop structure this entire thing is added to the loop structure for 4 to 3. So, now it is easy to see you know the loop structure for 10 to 3 obviously, we would add 3 then 10 then 8 gets added 7 gets added 5 and 6 and 4 all these get added to the loop structure automatically because they are all and for the back edge from 11 to 1 the entire flow graph would be all the nodes in the flow graph would be added to the loop structure. Let us change the back edge structure a little bit. So, instead of the back edge from 7 to 4 we actually change it to become the back edge from 7 to 3. Now, the loop structure of 10 to 3 does not change 11 to 1 does not change nor does the loop structure of 10 to 7 changes. The 2 loops which change are 4 to 3 and 7 to 3 7 to 3 now we add 3 then we add 7 we add 5 we add 6 then we add 10 8. So, this is going to be our loop structure. So, for 7 of course, we also add 4 right. So, 3 4 5 6 7 8 10 becomes the loop structure for 7 to 3, but for the back edge 4 to 3 there is a drastic cut. So, we add 3 then we add 4 to the loop there are no other you know predecessors of 4 apart from 3. So, the loop structure is just this particular small loop 4 to 3 3 to 4 and 4 to 3. So, by the back edge you know when we change the loop structure we have changing we are changing the back edge and once we change the back edge the number of nodes in the loop also get changed. So, this is the implication of the back edge on the loop structure. So, the next you know concept that we need to learn is the depth first numbering of nodes in a control flow graph why is this important. In the case of data flow analysis we know that we can visit the nodes of the control flow graph in any order, but I also mentioned that you know visiting the nodes in the depth first search order actually gives us fewer number of iterations compared to any other visit order. So, to understand the DFS order let us actually understand how to do the DFS numbering itself. We are really going back to the data structure course. So, this is a depth first search on the graph just that the numbering of the nodes is going to be slightly different. So, DFS num takes the node as a parameter then it marks the node n as visited as in any depth first search. So, for the for each node as adjacent to n. So, this is exactly the same as in the depth first search if S is unvisited then we add the edge n to S to the depth first search tree T. So, we are constructing the DFS tree and then we call DFS num. So, remember that we have not yet numbered the nodes. So, after all the nodes adjacent to n have been visited we come out of this loop and then number depth first num n as i to begin with i has been initialized to the number of nodes of the control flow graph and then once we do this we decrement i. So, this is the way it gets numbered. Now, a node gets numbered after all its dependence descendants or dependence get numbered the initial call on DFS num is through the entry node of the control flow graph. So, let us understand this algorithm with an example. So, here is the same simple control flow graph that we had before. So, we start our depth first search numbering from the block b 0 and so the counter has been initialized to 9 because we have sorry 8. So, that is because if we start the numbering from 1 then we have 9 nodes if we start the numbering from 0 then we have you know 0 to 8 again 9 nodes, but the numbering is going to be different. So, let us assume that the numbering happens from 1. So, we have initialized the counter to 9 this is the number of nodes in the control flow graph. So, we start our depth first search from b 0 its only neighbor is b 1. So, this is visited the next neighbor is b 2 that will also be visited then we visit you know say b 3 and then we visit b 5 and then we visit b 9 there are no more in the as you know adjacent nodes for 9 everything has been taken care of. So, because this is the next node which is adjacent, but we have already visited it. So, node b 7 gets the number 9 then we return to b 5 that would get its we have exhausted the adjacency list of b 5. So, it gets the number 8 then we go to node number b 3 now there is one more neighbor to be visited that is node number b 6. So, we visit that and then we find that all its neighbors that is only this has been visited already. So, we number this as 7 and then we return to b 3 that now has exhausted all its neighbors. So, it gets the number 6 then we return to b 2 we still have to visit b 4 and b 8 before we actually number b 2. So, we go to b 4 then we go to b 8. So, this has no neighbor. So, this gets the number 5 b 4 gets the number 4 then we return to b 2 which gets the number 3 then b 1 gets 2 and b 0 gets 1. So, this is the ordering in the depth first search numbering scheme. If we apply the same algorithm to this example we start with the node number 1. So, the numbering actually that already shows the depth first search numbering, but it is possible to number the nodes in a slightly different order also. So, for example, in the in this example suppose we had chosen to from here after visiting 3 suppose instead of going this way if we had chosen this path we would have come to b 4 and then b 8. So, this node would have been number 9 then this would have been number 8 and then we would have gone further to this part of the node this part of the graph. So, the order in which we visit the nodes will also change the depth first search numbering the same is true here. So, we start here then we go to 2 then we go to 3 4 then we visit say 6 then we visit 7 8 then we visit 10 and 11 no more. So, this gets the number 11 this gets number 10 we go back and then visit this gets 9 this gets 8 this gets 7 then we go back to 6 and then this will be this will cannot be numbered in the right now. So, visit this give it 5 and then 4 then 3 then 2 and 1. Whatever has been marked as marked in purple it corresponds to the DFS tree it is a spanning tree. So, these are the tree edges then the back edges are also called as retreating edges and whatever is neither a tree edge nor a cross you know retreating edge would be called as a cross edge. So, this is a cross edge here now what exactly is an inner loop. So, let us understand the inner loop there are unless 2 loops have the same header they are either disjoint or one is nested within the other. So, this is a simple property. So, if but if they have the same header then they need to be neither nested you know not disjoint I will give you an example of this, but otherwise assuming that this is not. So, we check whether the loops you know whether the nesting can be checked very simply by testing whether the nodes of a loop A or is a subset of the nodes of another loop B. So, if it is a subset then there is nesting. Similarly, if the loops have no common nodes then the loops are disjoint, but when the loops share a header neither of these may hold I will show you an example of this and if this happens they should loops share a header and neither nesting nor disjointness is the property we will have to actually combine the loops and transform it. So, let us take this big example. So, we have so many loops here 7 to 3 10 to 7 4 to 3 10 to 3 and 11 to 1. So, if you look at it 7 8 10 is a subset of this set the loop structure of 7 to 3. So, 10 to 7 is nested within 7 to 3 even though it does not appear. So, this is 10 to 7 and this is 7 to 3. So, even though it does not appear. So, it is indeed nested similarly 7 8 10 is nested in 10 to 3 and 11 to 1 as well that is you know visible then 4 3 is nested in 7 3 it is nested in 10 3 and it is nested in 11 1 as well. Now, it is also correct that 4 to 3 and 10 to 7 are disjoint loops. So, these two have no nodes in common. So, they are disjoint loops suppose we had a structure such as this. So, here for example, we have a node a loop which is this right there is another loop which is this. So, for in the loop structure the loop corresponding to the back edge c to a is a b c and the loop corresponding to d to a is a b d you cannot say that one loop is nested in the other and you cannot say that the two are disjoint because they still share a node. In such a case we actually want to transform this to this type of a control flow graph by adding a dummy edge. So, once we add a dummy edge we see that and we can shift the two back edges to rather combine the two back edges into one and make it edge from e to a. So, once we do that the for the back edge e to a the entire loop becomes a single loop. So, here also you know there are difficulties of that kind for example, 10 to 3 of course, there is no structure of this kind which appears, but nesting and disjointness definitely happen to be the case. So, this type of sharing headers, but neither being disjoint nor being nested is not true in this case. We also need to understand the concept of a pre header this will be required in the algorithm for loop invariant code motion. Suppose we have a loop structure with a header and there are many parts coming into this header it is usually convenient to make the header get just one input by actually separating all other inputs to go into a pre header. So, the semantics of this and this are the same it is just that the loop has now become very clean there is only one input from the outside and the rest of the edges are all only the back edges. So, whereas, here there are number of inputs coming from outside. The next thing very which is very important is to understand the convergence of a data flow algorithm I promise this in the last part of our lecture. So, what can we say about the number of iterations that the data flow algorithm iterative data flow algorithm takes. So, let us say we are given a depth first spanning tree of a control flow graph. So, we know how to construct the depth first tree we do the depth first numbering automatically we get the depth first tree. The largest number of retreating edges on any cycle free path in the spanning tree is the depth of the control flow graph. So, let us understand that and then continue. So, here the depth of the control flow graph is 2 that is because when we take the spanning tree which is shown in the picture and we consider the sequence of back edges 10 to 7 and then 7 to 3. This is the maximum number of back edges which can be traversed in the tree right all others would be just this is just 1 and this is again just 1. So, whereas, here we have 2 back edges which can be traversed. So, this is the depth of the control flow graph in this case whereas, in this case the control flow graph has a depth of 3. So, the reason is we go from 10 to 7 then we go from 7 to 4 and then from 4 to 3. So, the depth of this control flow graph is 3 what has the depth of the control flow graph got to do with the convergence of the algorithm. It is here the number of passes needed to for the convergence of the solution to a forward data flow analysis problem is 1 plus the depth of the control flow graph. So, this is the basic result. So, once we know the depth of the control flow graph we can say that this is the number of iterations needed for the convergence. And this convergence this number the this bound can actually be achieved if we traverse the control flow graph using the depth first numbering of the nodes any other order may actually take a few more iterations. One more pass is needed to determine no change and actually therefore, the bound becomes 2 plus depth of CFG. And for a backward data flow analysis problem the same bound holds, but we need to reverse the depth first search rather we need to consider the reverse of the depth first numbering of the nodes. So, instead of doing the traversal in the depth first search order we do it in the reverse order. Any other order will still produce the correct solution, but the number of passes may be more than what is actually predicted. So, that is about some fundamentals regarding control flow analysis. So, now we are ready to discuss the algorithms for machine independent optimizations. So, we are going to consider a few optimizations which are very common the first one obviously would be the global common sub expression elimination. And when we do this we will also need copy propagation and constant propagation is a very simple algorithm. So, we will look at that also and loop invariant code motion is something very different which is an application of the dominator relationship. So, we have already seen global common sub expression elimination a few times, but we did not discuss the algorithm formally. So, let us do that first it needs available expression information. So, we know how to compute the available expressions by data flow analysis. So, that would be a forward flow problem with the operator being intersection confluence operator being intersection. So, basically for every statement s which is of the form x equal to y plus z such that y plus z is available at the beginning of s s block. So, as the statement s is in a particular block. So, the you know rather y plus this is in a particular block and we want to make sure that y plus z is available at the entry of this particular block only then we can actually do some replacement. And we must also make sure that neither y nor z is defined prior to s in that particular block. So, s this is defined, but before that we should not have any definitions of either y or z otherwise y plus z which is reaching the entry point of this block you know will not be useful within the block. And we cannot do any elimination of the common sub expression because y and z have changed their value. So, in such a case if these conditions are met we search backwards from s s block in the control flow graph and find the first block which y plus z in which y plus z is evaluated. So, we actually have to do this for all the parts which go backwards from s. So, we need not go through any block that evaluates y plus z we just have to go until y plus z is evaluated that is it. And that makes sure that y plus z is available right. So, availability is already satisfied now we are finding the blocks in which y plus z is evaluated. So, once we find these blocks. So, we are actually going to replace you know this rather we are going to create a new variable u and replace each statement w equal to y plus z by u equal to y plus z and w equal to u. We saw this already this is just a formal statement of that and then we replace s by x equal to u. So, this we repeat for every processor of the block s and we have already seen that repeated application of GCSC may be required to catch deep common sub expression perform deep common sub expression elimination. So, let me again show you the same example. So, here is y plus z assume that y plus z is available at the entry point of this basic block. So, these are the three paths emerging out of s. So, along each path we go up to the block which evaluates y plus z here here and here then create the same temporary u equal to u and then insert u equal to y plus z u equal to y plus z and u equal to y plus z and of course, k equal to u l equal to u and m equal to u. So, now this statement can be replaced by x equal to u. So, we have eliminated the common sub expression here you know this is it is evaluated only once here and then reused. So, what we mean by a deep common sub expression is one which surfaces only after one round of GCSC and one round of copy propagation. So, we have seen this example before also briefly let us see what it does. So, we have x plus y and x plus y here. So, first round of GCSC makes this u equal to x plus y and this is equal to u then a round of copy propagation actually you know makes u star z visible as a common sub expression. So, we can eliminate that also by the same algorithm and thereby improve the efficiency of the code. So, this is what we mean by a deep GCSC. Deep GCSC is SES rather common sub expression and deep common sub expressions actually surface only after one round of GCSC and copy propagation. On the running example there are many places where GCSC is possible. So, here is i minus 1 and here we have 4 star j and then we have you know this 4 star t 6 which will surface later. So, 4 star j has been eliminated you know and then we have other expressions which are possible. So, j plus 1 is possible here and so on and so forth. Then so after one round of GCSC we get this we have still not studied copy propagation. So, we will defer applying copy propagation to the stage after studying this algorithm for copy propagation. So, the next algorithm that we want to understand is the copy propagation algorithm this is a very important algorithm and it is non trivial to solve. The purpose of copy propagation is to eliminate copy statements of the form x equal to y. So, what is this copy propagation we have a large number of users of x and let us say neither x nor y change in before we reach the usage of x. So, in such a case we can replace all these users of x by the value y by the variable y. So, thereby we have actually you know eliminated this copy statement. If x is not used later after all these copy propagation has taken place then we can eliminate the copy statement x equal to y. To do this there are two major conditions which need to be checked. The first major condition is the use definition chain of use u of x must consist of s only. So, here in other words s is the only definition of x reaching u. So, there is a usage of x. So, this copy must be the only definition of x reaching u if there is one more then obviously, we do not know which value is valid at u. So, we cannot actually replace x by this y whereas, if this is the only copy which is reaching this u then we can replace the use of x here by y. This is fairly straight forward to do we can use the use definition chain of that is available that can be constructed using the reaching definitions. The second condition is more complex on every path from s to u including paths that go through u several times. So, cycles are ok for u, but they do not go through s a second time. So, definitions cannot be gone through a second time. There are no assignments to y. So, if there is an assignment to y then the value of y changes. So, we cannot reuse it. This ensures that the copy is valid. So, again this is the reason why we cannot go through the definition in a second time, but we can go through the usages any number of times. That is not an issue and the value of y must not change. The value of x should also not change, but that will be automatically taken care of when we check this. So, we will see that now. To check the second condition is non trivial. So, we need to formulate a new data flow analysis problem. So, let us formulate the problem of reaching copies as we can call it. So, in this problem we again define C gen and C kill and C gen is the set of all copy statements x equal to y in B such that there are no subsequent assignments to either x or y within B after the statement s. So, this is the generation of copies which copies reach the end of the basic block B. So, this is very trivial almost. If a copy x equal to y has to reach the end of the basic block then neither x nor y must be modified within the block after this copy. So, what is the C kill set? It is a set of all copy statements x equal to y and s is not in B. So, very similar to the reaching definitions and available expressions problem. So, there is a you know an assignment to either x or y assigned a value in B. So, an assignment to either x or y not necessarily copy just assignment to either x or y. So, this x or y assignment kills the copy involving x or y. So, if there is a copy involving x or y x and y then assignment to either x or y in the basic block B will be killed. So, we are considering the copy statements all over the program, but the assignments within the basic block this is what we did in the reaching definitions and available expressions problem as well. Let you be the universal set of copy statements in the program. So, what is the C in of a basic block? C in of a basic block is a set of all copy statements x equal to y reaching the beginning of the basic block along every path such that there are no assignments to either x or y following the last occurrence of x equal to y on the path. So, this is something intuitive we just want to make sure that when a copy statement reaches the beginning of the block either x or y have not been assigned a value on that path. So, the same is true for C out. So, it is a set of all copy statements x equal to y reaching the end of the basic block along every path such that there are no assignments to either x or y following the last occurrence of x equal to y on the path. So, after the copy statement we should have no modification of either x or y. So, that is the meaning of both C in and C out. Here are the data flow equations for computing the reaching copies. So, just like the available expressions problem the confluence operator is intersection and this is a forward flow problem because out is being computed in terms of in. And because of the confluence being intersection we have the same initialization as in the available expressions problem C in of b 1 is phi permanently and C out of b is u minus c kill for b not equal to b 1. So, initialization is using the universal set. So, we could also have said C in of b 1 C in of b equal to u. So, for b not equal to b 1, but this is also fine. So, now C out of b is simple. So, take all the copies which generated in the block and then the copies which come into the block at the entry point remove what is killed in the block. So, that is what C out b is this is very intuitive just like the reaching definitions and available expressions problem. And in the case of C in it is an intersection of all the copies of the predecessors. So, again this is intuitive because the same copy must reach the input point via all the predecessors of the basic block b. So, I will show you in the example that mere format of the statement being x equal to y will not make the copy the same it may be a different copy. So, let us see how to actually do the copy propagation. So, we have computed the reaching copies for each of the basic blocks. So, for each copy statement x equal to y we use the definition use chain which is nothing, but a link list of you know linking a definition to all its uses. So, determine those uses of x that are reached by s. So, to compute the d u chain is it is a different problem very similar to the live variable analysis and that is left as an exercise. So, using the d u chain determine those uses of x that are reached by s. So, this will this is just to make the you know checks easy that is all. So, we look at all the uses of x these are the potential places where y a replacement by y can be made. Then for each use u of x found in one we need to check several conditions u d chain of u consist of s only. So, this implies that s is the only definition of x that reaches the block. So, I already mentioned this if there is more than one definition reaching the use u then we are not sure whether it is the copy or the other definition. So, we cannot get rid of the you know x in this usage u s is in the c in of the basic block where b is the block to which u belongs the usage u. So, this make sure that no definitions of x or y appear on this path from s to b. So, this was taken care of in the definition of in itself. So, that is why this is a restatement of a repeated statement of the same property. No definitions of x or y occur within b prior to u found in one. So, this is within the basic block this is across the basic block. So, both have to be made sure of if s meets the conditions above then remove s replace all uses of x found in one above by y. So, we have to be very careful here we must make sure that every usage of x satisfies these properties. Even if one of them does not then we cannot get rid of this copy only when all usages of x satisfy these properties we can replace those usages by y otherwise we cannot get rid of the copy statement. Here is an example of copy propagation adopted from the book. So, we have s 1 which is a copy statement x equal to y s 2 is another copy statement p equal to q s 3 is not a copy statement s 4 is a copy statement x equal to z s 5 is a copy statement x equal to z. So, here I want to emphasize that the copy x equal to z in the block b 2 and the copy x equal to z in the block b 3 are two different copies they are not the same even though their form is similar. The assignment 2 x happens in both the right side is the same, but these two copies are not the same. So, once we have two different statements then the two are different copies unfortunately the copy propagation algorithm is not powerful enough to capture the effect of these two copies being the same. So, even though in this case they are indeed the same our algorithm cannot capture this effect then s this is not a you know s 6 is not a copy statement s 7 is also not a copy statement, but s 8 is. So, to compute the gen and kill I have not shown it here, but it is very trivial. So, here x equal to y and p equal to q are both generated by this basic block and when we consider the kill look at x equal to y. So, all the copies involving x are killed by this copy. So, this is killed this is killed so and that is it these two are killed. So, that is that is it similarly for others as well. So, we will not worry about the gen and kill because it is very simple to compute them. Let us understand what the structure of c in and c out are. So, this basic block has no incoming statement. So, c in of b 1 is always 5 in the case of c out obviously s 1 and s 2 reach this point. So, they are included in the outset for b 2 c in is nothing, but the outset of b 1. So, that would be s 1 s 2 and c out will we actually have x equal to z. So, this is a copy which goes out. So, s 2 s 4 is included and since s 4 is assigning to x the preview this copy x equal to y would be killed. So, that is removed from this set. So, we have s 2 and s 4 obviously statements which are not copies are not included in this set. For this block again c in would be the out of this. So, that is s 1 s 2 and out of this block would be obviously, s 5 is included and it is assigning to x. So, this s 1 would be removed. So, s 2 and s 5. So, here you can see that s 4 here and s 5 here are being maintained as two different copies. In this particular you know block before which has s 6 there is a usage of x here. So, since our copy which reaches from here is nothing, but x equal to z and all the conditions of the copy propagation are satisfied here x and z are not modified here they are not modified here. So, this x has only this particular copy as the reaching definition the rather the defining occurrence. So, this x can be replaced by this z. So, this can really become z plus 6 absolutely no problem about that. When we come here this has another x unfortunately the x which comes from here is s 5 and the x which comes from here is s 4 even though these two are really the same copies they are being you know represented as different copies. So, since there are two definitions or two copies reaching this usage of x we cannot actually replace it by z. Now, there is n equal to p here p has a you know definition in s 2 and that reaches along this path and along this path as well with no modifications to either p or q. So, we can replace n equal to p the p in n equal to p by q and this would essentially become n equal to q. So, this is how the copy propagation algorithm works and let me stress again that even though syntactically these two copies are the same they are being treated differently by the algorithm. On our running example there are many copies here. So, for example there is t 10 here which can be replaced by t 4 and there is t 12 here which can be replaced by t 4 and then there is t 14 here which can be replaced by t 6 etcetera. So, once we do that we get this and it exposes the next level of common sub expressions which can be eliminated. So, we get this right. So, we can now do another copy propagation on this and finally we get a very condensed piece of code. So, that completes our available sorry the copy propagation algorithm. So, now let us move on to the next optimization which is simple constant propagation and constant folding. In fact, constant propagation you know is very similar to copy propagation it is just that the right hand side of the copy now becomes a constant value. So, the algorithm has the same flavor as before, but it is not necessary to solve a different you know data flow analysis problem here. So, we use a statement pile which consists of all the statements in the program. So, we take one statement at a time and then see what to do with it. So, while statement pile. So, we include all the statements in the program into this. So, while statement pile is not empty remove the statement if the statement is not a you know statement of the form x equal to c where c is a constant then we ignore the statement and we actually go to the next statement in the pile. If it is indeed a statement of the form x equal to c then we check all the statements t in the definition use chain of x. So, that means we check the x is definition because it is of the form x equal to c. We see all the usages of x and this d u chain is a very convenient data structure to examine all such usages. Now, if the usage of x in t is reachable only by s. So, this can be checked using the u d chain. So, let me show you what this really means. So, here is a constant definition x equal to 7. Let us say there are two usages for this definition one is here u 1 and the other is here u 2. So, the d u chain consists of both these usages. In the case of u 1 this is the only definition which is reaching this u 1. So, we can simply replace this x by 7 and also simplify this expression 7 plus 6 as 13. So, no problem with that, but if you consider u 2 there is another definition d 2 x equal to 9 which is reaching this x. So, is the value of x here is it 7 or is it 9 that cannot be determined at compile time and therefore, we actually do not replace this x either by 7 or by 9 we just leave it as it is. And if we do that then you know it is not possible to remove either this statement x equal to 7 or x equal to 9. So, this is what we mean by the statement if usage of x in t is reachable only by s. So, if it is true then substitute c for x in t then we simplify the statement t. So, I showed you this already here we could have simplified this to 7 plus 6 why should we do this. Suppose this was y equal to x plus 6 and now after simplification this becomes y equal to 13. So, this is another constant assignment. So, now all usages of y can be actually potentially replaced by 13. So, that is the reason why we require this simplification of t and after simplification we added to the statement pile. This is again required because if this had become y equal to 13 this is a new statement which was not present before. So, we must add it to the statement pile. So, that we examine all the d u chain of y etcetera etcetera as well. So, the statement pile now gets another extra statement now the same loop is repeated until the statement pile becomes empty. So, in this case after the usages of x are replaced by c then x equal to c possibly becomes dead code. If there is still you know some usage of x which is not replaced then it is not dead code otherwise it is dead code and a separate dead code elimination pass can remove such code. So, that is not an issue what have we not done here actually we have not performed what is known as conditional constant propagation this is only simple constant propagation. In the case of conditional constant propagation we consider conditions in the if then statements and if the conditions evaluate to a constant value then we need to choose either the true branch or the false branch at compile time itself the other branch becomes redundant. So, this type of conditional constant propagation is a little more complicated than the simple constant propagation and when we consider the static single assignment form which is more effective for conditional constant propagation we will consider examples and the algorithm to perform it. Thank you we will stop here today and continue the next part thank you.