 Welcome to part 6 of the lecture on machine independent optimizations. Today, we are going to discuss machine independent optimizations and continue with static single assignment form and so on. In the last part of this lecture, we discussed you know global common sub expression elimination and copy propagation and along with it we also saw a simple version of constant propagation. Today, we will see how to perform a major optimization called the loop invariant computation detection and code motion. So, informally we already know what loop invariant code is, but let us understand that with this example properly. So, the statements marked in red in this particular program are said to be loop invariant code. The reason is T 3 has been assigned a constant value address A, which is an offset for the array A inside the activation record and T 4 is nothing, but T 3 minus 4 again it is a constant. So, these two statements do not get any different values even you know during any number of iterations of this particular loop. So, it seems likely that the stroke can be shifted to a point just outside the loop and this is exactly what is called as code motion. These are said to be loop invariant code and moving them outside is the code motion part. So, after code motion we would get a program in this form this is more efficient obviously, because these two statements execute only once and they do not execute as many times as the loop does, but moving you know detecting invariant code has a many steps and similarly code motion should satisfy many conditions before the code can be moved out. So, in the case of loop invariant code detection the algorithm is quite simple. So, let me explain the algorithm with this example before we go to the text of the algorithm to begin with in initially when the algorithm starts. We mark the statements which have some x equal to you know some constant type of a computation or x equal to some value and that value variable and that variable actually has a reaching definition only outside the loop. In both these cases the statement is not going to alter its value during the iterations of the loop. So, therefore, such statements as such as this you know either assigned a constant value or assigned some expression whose operands have reaching definitions strictly outside the loop. In both these cases we mark them as loop invariant in the first step. So, once such statements are marked as loop invariant for example, here t 3 equal to address a in the second iteration of the algorithm we would mark other statements which are dependent on such loop invariant statements. For example, here t 4 equal to t 3 minus 4 involves t 3 and the statement corresponding to t 3 has already been marked as loop invariant the other operand is a constant. It is possible that instead of a constant here we could have another operand which is also marked as loop invariant or this operand could have its reaching definitions outside the loop. So, such dependent statements are marked as loop invariant in the second iteration. The third iteration considers the statements which were marked invariant in the previous iteration and this process continues until we cannot mark any more statements as loop invariant. So, that is what is stated in the formal version of this algorithm here. Mark as invariant those statements whose operands are all either constant or have all the reaching definitions outside L. So, this is the first step and then we have a loop now mark as invariant all those statements not previously. So, marked all of whose operands are constants or have all the reaching definitions outside L or have exactly one reaching definition and that definition is a statement in L marked as invariant. So, this is the dependence part which I explained. So, this loop goes on until no new statements are marked as invariant. So, this is the detection of loop invariant computation, but just detecting computations as loop invariant does not take us anywhere you know. So, it does not actually improve the code we should be able to move the code outside the loop. So, to do that we have a couple of statement conditions to check. The algorithm is simple first of all find loop invariant statements and then for each statement is defining x found in step one check these three conditions and then we will come to the details of these conditions very soon. Move each statement as found in step one and satisfying the conditions of step two to a newly created pre header just before the loop. The only requirement here is provided any operands of s that are defined in the loop L have previously had the definition statements moved to the pre header. So, in other words if you are moving a statement s the this statement would have its operands probably defined within the loop L. So, if so we have to move those statements which supply operands to the statement s you know they must be moved to the pre header earlier than the statement s. In other words we should maintain the order in which these statements execute. Now, coming to the conditions that the statement s must satisfy the first condition is s is in a block that dominates all exits of L. The second condition is x is not defined elsewhere in L and the third condition is all uses of L of x can only be reached by the definition of x in s. So, I am going to show you you know examples where violation of any one of these conditions can lead to incorrect code motion. So, it is possible to you know show that these are all requirements for code motion and that is actually that proof is available in literature, but we are not going to discuss the proof here I am going to give you examples. So, condition 2 a states that s dominates all exits of L. So, let us take the program which is here. So, in this program the block b 3 has been highlighted and specially the statement i equal to 2 is what we are targeting. So, i equal to 2 is a statement which can be marked as loop invariant quite easily because it has only constant operands. So, once having done that can we move this statement to outside the loop. So, at to this point create a pre header and put it in that pre header can we do that. The answer is no because condition 2 a which states that s dominates all exits of L is violated here. So, for example, if you consider b 3 then it is in this particular loop which consists of u b 3 and b 4, b 4 is the exact for the loop and b 3 definitely dominate look at all the entry paths from the starting point b to b 4. So, we can actually go through the path b 1 b 2 b 4 without going through b 3 therefore, b 3 does not dominate b 4. So, this is a violation of condition 2 a let us see what happens if we still move the statement i equal to 2 to a pre header just outside the loop. So, the statement will be immediately after i equal to 1. So, in this case because i equal to 2 will be immediately after i equal to 1 i will get the value permanently as 2 this i equal to 1 can never be achieved in practice. So, this goes here. So, i equal to 2 will be executed immediately after i equal to 1. So, i will get the value permanently as 2. So, in such a case once we go to j equal to b 2 whereas, yes once we go to j equal to b 2 whereas, in the case we are going to go to it is possible to take this path directly and in that case j will have the value 1. So, this is a you know clear violation of the semantics of the program. The program does not you know behave as it used to after code motion therefore, the code motion is illegal. So, now, so this is what I implied here is i equal to 1 we have mode i equal to 2. So, this program is illegal. So, let us look at the running example of bubble sort let us see if there was any possibility of code motion. So, j equal to 0 is obviously, a loop invariant computation that is very clear it is inside this loop right and it has only constant operand. Now, can we move j equal to 0 to the pre header just after b 1 outside the loop. So, now, the condition b 3 you know condition that b 3 which is this particular basic block dominate the exits of l which is b 9 is violated. So, this particular basic block does not dominate b 9 that is easy to see because from here you can go out directly to b 9 without going through b 3. And as is very clear the intuitive understanding of our bubble sort program tells us that j a loop is inside the i loop and j will be initialized to 0 every time. So, if we move j equal to 0 outside the loop this initialization will not have will not happen in every iteration of i and therefore, it will be the program will not run correctly. So, that is an intuitive understanding that is obviously correct. So, the second condition is condition to b x is not defined elsewhere in l. So, consider this modified program we have b 2 in which we have i equal to 3. So, i equal to 2 was here now we are not considering b 3, but we are considering b 2 which has i equal to 3 right. Now is condition 2 a satisfied s dominates all exits of l. So, in this case b 2 definitely you know satisfies this condition because b 2 dominates b 4. So, all paths from b 1 must go through b 2 in order to reach b 4. So, that is very easy then can we now move i equal to 3 to a pre header here. So, it will be after i equal to 1 again if we do that then the semantics of the program will not be correct. So, let us see why. So, if the loop is executed twice that is we start here let us do this with i equal to 3 in this place itself. So, we go through this loop right once then we actually go back and let us say then we go through this i equal to 2 and then we go back and then we could actually go through this loop once more like this right. So, it is possible that i gets the value 2 and then goes out or after a couple of iterations again you know we could get i equal to 3 and then go out. So, in this case j can possibly get the value 2 and sometimes it can get value 3, but suppose we you know move this computation outside the loop right. Suppose i equal to 3 is moved to this point. So, i equal to 3 will not be executed in every iteration of the loop now it will be executed on entry to the loop. So, i equal to 1 and then i equal to 3 that is it. Now, if we take the path this path all the time then the j can definitely get the value 3, but suppose this loop was executed once and then we had taken this path then i would get the value 2 and i can never get the value 3 again because i equal to 3 has been moved outside. So, the value of j here will be always 2 even if we have traverse this path just once you know if we traverse it once then j becomes 2 and can never become 3 whereas, in this original loop even if we had traverse this path then i would have got initialized to the on entry to the loop again. So, in the next iteration we would have initialized i to 3 and there was a chance for j to get value 3 if this path was taken. So, the semantics of the program have changed and therefore, the transformation of moving i equal to 3 to the block just outside the loop is incorrect. So, what happens in our running example in the running example in this case we had j equal to 0 here the semantics will not change if we move j equal to 0 here because this is still within the loop. So, we could have actually moved j equal to 0 to this place. So, like this now this b 2 definitely dominates b 9. So, condition 2 a is satisfied, but j is defined in b 7 also. So, this is a violation of condition 2 b. So, we have defined j here and we have defined j here in the previous example we had defined i here and we had defined i here also there were two definitions of i and that was a violation of condition 2 b. Here also we have a definition of j here and definition of j here and this you know j plus 1 which is here can be reached by this j equal to 0 and it can also be reached by this j equal to t 6. So, because of this the value of j plus 1 can be quite different whether we get when we get j equal to 0 or the other one. So, when violation happens and the semantics of the program obviously will not be correct here also condition 2 c states that all uses of x in L can only be reached by the definition of x in S. So, again let us consider this program with a slight modification i equal to 2 is a requirement in the block b 4. Now, the we really have k equal to i in the block b 6 this i here gets its value from this i one possibility it can also get this value through an iteration of the loop 2 second possibility. So, there are two definitions which reach the usage of i in block b 6. So, that means the condition 2 c has been violated which says all uses in x of x in L can only be reached by the definition of x in S. So, correctly you know if you wanted to move this code to some other place only this statement should have supplied its value to i here, but we now have this and this both of them supplying value to i. So, we cannot move this code i equal to 2 to the player just after what we do we move the code irrespective of this condition. So, then again i gets the value 2 here and k will always get the value 2 it cannot get the value 1 at all. So, this changes the semantics of the program and therefore code movement in this case is incorrect. Again in the running example we have you know b 2 dominates b 9 and j plus 1, but j plus 1 in b 5 is reached by j equal to 0 and j equal to t 6. So, because of this the condition 2 c is violated and we cannot move this code to outside the loop. So, that is about the loop you know machine independentization on the static single assignment. So, the lecture consists of the following parts we will discuss the static single assignment form its definition with a few examples and we are going to discuss some of the optimizations with the SSA forms such as dead code elimination, simple constant propagation, copy propagation and the most important of them all conditional constant propagation and constant folding. This last optimization is said to be very effective with static single assignment form as compared to the non SSA form that is a control flow graph. What is a static single assignment form? This is a new intermediate representation and it incorporates diffuse information. So, in other words the definition is actually incorporated into this new static single assignment. So, a very variable has exactly one definition in the program text. So, that is why this is called as a static single assignment form, but I must hasn't to add that this does not mean that there are no loops. The program definitely can have loops without loops you cannot write meaningful programs anyway. The static assignment should be highlighted. So, this is a static single assignment form and not a dynamic single assignment form. In other words we are making sure that in the program text as it is given exactly one assignment to each variable exists. It does not dynamic means in the loop the statement would be you know assigned other variable will be assigned values again and again. So, this dynamic assignment is definitely permitted multiple assignments are permitted, but what we mean is in the program text that is static text a single assignment will is to be asserted. So, loops are permitted, but within the loop again the text must not have more than one assignment to the variable. So, optimizations perform much better on the static single assignment forms. The most important of them all is the conditional constant propagation and other one is the global value numbering. These two are faster and more effective on the static single assignment forms. SSI is a sparse intermediate representation. So, what do you mean by sparse? If a variable n variable has n uses and m definitions then the diffuse chains require space and time proportional to n dot m. Whereas, the corresponding instructions of users and definitions are only n plus m in number right. So, there are n uses and m definitions. So, if you just add up it is n plus m SSI form for most realistic programs is linear in the size of the original program. So, this is called as a sparse intermediate representation. So, more linear in the size of the original program. Let me give you an example. This is a fairly simple example. Here is the program text. So, what we have done here is show you the program in non SSI form and the flow graph in SSI form. So, this is a simple max computation read a b c if a greater than b then if a greater than c max is a obvious otherwise max is c. Now, if b greater than c then max equal to b otherwise max equal to c. So, print max. So, the flow graph faithfully says read a b c then it checks a greater than b if true it checks a greater than c if false it checks b greater than c. So, so far there is no change in the flow graph corresponding and the corresponding program in non SSI form. The change now happens now. So, we have the same variable max in all four cases as in the static single assignment form we are not permitted to use the same variable again and again for assignment. So, we would have max 1 equal to a here you know max 2 equal to c here max 3 equal to b here and max 4 equal to c here. So, there are four assignments and each of them uses a different max variable. If we do that the we run into a problem at this point we are supposed to print the value of max and there are four values of max. So, which one of these max values should be print. So, this is a common problem at a join we are supposed to have a single value of the max variable to get that the static single assignment form introduces a merge or join operator called as phi. So, this merge operator takes as many operands or parameters as the number of incoming arcs. So, we have max 1 and max 4. So, we have four of four parameters here. So, there are four different values which are possible along the four different incoming arcs. So, it has four parameters. The cement and it is written as max 5 equal to because again we cannot reuse any of these names. So, we create a new name and make it max 5 and what is printed here is max The semantics of the merge operator states that the value of the operand or the parameter which is relevant or picked up is the one corresponding to the flow of control. So, if the flow of control actually happens via this path we actually enter this basic block via this basic block then we would have taken this edge to enter it. Then this is the first edge this is the second edge this is the third edge and this is the fourth edge. So, if the entry happens via edge 1 then parameter 1 will be taken as the value if it is through edge 2 then the second one if it is edge 3 the third one and if it is edge 4 then the fourth one. So, that would be the value of phi. So, this is the semantics of phi statement. Obviously, this phi does not have any physical machine instruction corresponding to it in any machine. So, we usually we are required to translate it to you know copy instructions and so on and so forth. So, but that would be done at the time of machine code generation. So, we are not going to worry about it during the optimization phase. So, let us define the SSA form now that we have seen an example a program is in SSA form if each use of a variable is reached exactly by one definition. So, this is the static reflation part flow of control remains the same as in the non SSA part a special merge operator phi is used for selection of values in joint nodes. I already explained this not every joint node requires a phi operator for every variable well be no need for a phi operator if the same definition of the variable reaches the joint node along all incoming edges. Even in the previous example we do not have you know this is the joint node we do not have any merge operators for a b and c because it is not required. The value of a b and c do not change along any path therefore, there is no need for a b and c to have merge operators at this point. So, this is what it says no need for a phi operator if the same definition of the variable reaches the joint node along all incoming edges often an SSA form is augmented with U D and G U D U chains to facilitate design of faster algorithms we will see this later. Translation from SSA to machine code introduces copy operations which may introduce some inefficiency, but hopefully good register location takes care of this inefficiency. So, now let us discuss another example of the SSA form. So, this is the program in non SSA form or the flow graph in non SSA form and this is the program in SSA form. So, we have only two variables in this program i and n. So, i is assigned a value and n is actually read from input. So, that is as good as an assignment and then we compare n with 1 if it is true then we check whether n is even then again if it is true then we have n equal to n by 2 if it is false we have n equal to 3 star n plus 1 both of them join here we increment i and go back. So, if this was false then we would have if n not equal to 1 we would have if this was false then we would have come here print i and you know gone ahead stop. So, let us see how this can be you know how the SSA form reads. So, we have i equal to 0 here and then we also have i equal to i plus 1. So, we have two assignments to i therefore, we require more than one i variable. So, we have i 1 equal to 0 at this point then we have read n. So, we have read n 1. So, that is the first variable for n there are many assignments to n again. Now, this is a join point right at this point we have a join. So, there is a value of i which flows along this arc and there is a value of i which flows along this arc. So, obviously these two values can be quite different and therefore, we have a phi variable here we have a phi operator here the value of i let us say is i 3 at this point that is one which flows along this direction and the value of i which comes along this arc is i 1. So, we have a selection between i 3 and i 1. Obviously, the variable i for i which is going to collect the value from the phi operator is also an assignment and that requires a different instance of i. So, here is where we have i 2. So, we have i 1 and then i 2 which just collects the value from the phi operator and then we have i 3 which is the assignment in this block b 7. So, similarly so, that takes care of i 2 rather i. So, remember even though there are only two assignments to i it does not mean we require only two different instances of i. If we had just made this i 1 and this as i 2 then you know getting the appropriate value of i here through the phi statement would have made this i 3 instead of i 2 that is all. So, that is nothing but just a you know naming convention and nothing more than that the naming or renaming algorithm takes care of all such minor problems. Then you know there is a value of n which comes in here and a value of n which comes in because of these as well. So, assuming that we have a value you know of n which is coming in this direction as n 3 and the value which comes along this direction as n 4. We require a phi operator here to supply the value to this particular node. So, that would be called as n 5 equal to phi of n 3 comma n 4. So, depending on the entry either this or this one of these will be picked up that will be called as n 5 and supply to this particular join node. So, if the entry is via this arc then we pick up n 5 and if the entry is via this arc then we pick up n 1. So, that is given the variable name n 2. So, n 2 is compared here instead of n not equal to 1 n 2 is compared here then we check whether n 2 is even and then because there is an assignment to n we introduce a new variable n 3. So, that gets the value n 3 equal to n 2 by 2 and here we have to introduce a fourth variable n 4 which is 3 star n 2 plus 1 and because of these two different variables joining at this point we require a phi operator here which picks up one of these values and supplies it to this node. So, even though we had 1 then 2 and 3 assignments to n we have introduced really you know this is n 1 here is n 2 then we have n 3 n 4 and n 5. So, 5 instances of the variable n have been introduced even though the original had only 3 assignments to n. So, the rules for introducing the phi operators and new assignments is a bit complicated we are not going to discuss the construction of the SSA form which takes care of such difficulties. Let us look at one more example. So, this is actually a straight forward program in which we have L S R, R S R and S R as the main variables then there is a t equal to S R star S R. So, then we compare t greater than a etcetera etcetera if it is true we assign R S R equal to S R if t greater than a is first then we compare t less than a if that is true we have L S R equal to S R otherwise we have L S R equal to S R and R S R equal to S R there is a joint point here we compute S R as L S R plus R S R by 2 then L S R not equal to R S R we go back and iterate. So, do not bother about the meaning of the program it really does not do anything meaningful it has been constructed just to show the SSA version. So, here is a you know L S R computation. So, we have L S R 1 L R S R 1 and S R 1 right then we have you know a joint block here. So, there is a value of L S R R S R and S R given here and through this arc we again get versions of L S R R S R and S R rather S R is not supplied here S R is computed right now it is computed it is actually supplied. So, we have S R version also coming in here. So, for that we have new variables for L S R R S R and S R which are actually the collecting variables for the phi operators then we compute t. So, then we go on to check t against A we have new instances of R S R L S R and S R generated here and once we do that you know we again require a phi function here this requires a phi function because there is a version of L S R coming here which is the old one and then a new value of L S R comes here and another new value of L S R comes here. So, there are three different values of L S R coming in through these three paths. So, we require a phi operator with three operands the same is true for R S R and of course, S R is computed here. So, that is a new variable as well. So, this is the way the SSA form appears it has a phi operators whenever we need to pick a corresponding value you know with each entry point. So, now let us discuss the optimization algorithms the first one is dead code elimination which is very simple. The reason why it becomes simpler than before is that there is exactly one definition reaching each use by the way let me show you that here. So, in this new version SSA version this definition n 3 equal to n 2 by 2 reaches this use. So, that is the only thing and then we have this n 3. So, again that is corresponding to this definition. So, we do not have more than one definition reaching any use. So, this is reached by this and then the n 1 is reached by this and the n 2 is a new definition which will reach all these uses this and this. So, exactly one definition reaches every use. So, because there is only one definition reaching use use we examine the d u chain of each variable to see if it is used list is empty. If there is no use list then obviously, the code is dead remove such variables and their definition statements. And if a statement such as x equal to y plus z is deleted we must take care to remove the deleted statement from the d u chains of y and z as well. So, obviously if this goes away this y and this z will not be valid uses anymore. So, we must remove the quadruple numbers of these statements from the definition of y and z the same is true for the 5 statement as well. So, let us consider the simple copy constant propagation copy propagation and then conditional constant propagation global value numbering is a bit more complicated. So, we are going to skip that simple constant propagation is really simple it is actually a simplified form of the constant propagation we studied with control flow graphs. So, we form the statement pile as before s is a statement in the program. And while the statement pile is not empty we execute the loop we remove a statement from the pile and if s is of the form x equal to phi of c comma c comma c comma c for some constant c. Then we can replace this statement by x equal to c that is very obvious and now if the statement is of the form x equal to c either obtained by this simplification or otherwise for some constant c. Then we delete s from the program because we do not have to perform any dead code elimination we will see why for all statements t in the d u chain of x we substitute c for x in t. So, obviously the check that we did in the control flow graph whether the you know whether there is more than one definition reaching x that need not be this use of x need not be done here exactly one definition reaches. So, we can simply substitute c for x in t and we simplify t. So, we can definitely do this for all these statements in d u chain and once we do that this x equal to c becomes redundant therefore, we can do that elimination right here we could not have done that in the control flow graph. Now, we add t to the statement pile and we go back. So, this is a very simple constant propagation copy propagation is very similar to constant propagation a single argument phi function x equal to phi y or a copy statement x equal to y can be deleted and y substituted for every use of x. We do not have to check anything here because exactly one definition reaches the use of x that is this definition reaches the use of x here and no other definition can reach that point. So, copy propagation is very simple here let us now consider conditional constant propagation static single assignment form with extra adjust corresponding to the definition use information are used here. So, this is the augmented version of the s s a form edge from every definition to each of its uses in the s s a form hence forth we call these edges as s s a edges these are added to the s s a form. It uses both the flow graph and the s s a edges flow graph edge and the s s a edge and it maintains two different queues are work less. So, one of them it does not mix up the two kinds of edges one of them is called the flow pile which stores the flow graph edges and the other one is called the s s a pile which stores the s s a edges. So, flow graph edges are used to keep track of reachable code. So, there so flow graph edges indicate you know the flow of control. So, we are going to use that to keep track of reachable code. So, if we cannot reach some code using a flow graph edge as we will see in the example then you know this is unreachable code and it can be eliminated. Then what is the purpose of s s a edge s s a edge is useful in the propagation of values. So, what happens is it is cumbersome to traverse the control flow graph every time just to check the changes which happen in a basic block because of a definition change. Such you know changes can be easily propagated using the s s a edge. So, we do not have to traverse the whole control flow graph to come to a particular basic block. We can directly go to that basic block and look at the changes that have happened there. So, the algorithm actually becomes much faster because of the s s a edges. If we had used only flow graph edges we could still have performed constant propagation, but now with s s a edges the program works much faster the constant propagation program becomes much faster. Flow graph edges are added to the flow pile whenever a branch node is symbolically executed. So, or whenever an assignment node has a single successor. So, when we execute a flow graph you know a branch node we may take the true edge or the false edge in case the condition actually evaluates to true or false. If it the value is not known then we have to actually add both the edges to the flow pile. Whenever an assignment node is executed and it has a obviously a single successor then that edge is added to the flow graph pile flow pile because the successor node of the assignment node is the one which would be executed after the assignment. s s a nodes coming out of a node edges rather coming out of a node are added to the s s a pile whenever there is a change in the value of the assigned variable at the node. So, this becomes very clear as we go along. So, if there is a definition and the variable associated with the definition changes its value then the s s a edge corresponding to that node will be added to the s s a pile. This ensures that all uses of a definition are processed whenever a definition changes its lattice value. So, the lattice value that I am you know referring to here is the abstract lattice semi lattice value that we had discussed before that is the top or the constant value or the bottom. So, the same lattice is used by the conditional constant propagation as well. This algorithm requires much lesser storage compared to the non s s a counter part and conditional expressions at branch nodes are evaluated and depending on the value either one of the outgoing edges corresponding true or false or both edges are added to the flow pile work list. However, at any joint node the meet operation considers only those predecessors which are marked as executable. So, this is very important if the if a particular node cannot be reached via a predecessors which is you know which is indicated by the corresponding incoming edge being not marked as executable then we do not have to bother about that incoming node at all. So, here is the first example. So, we have start then a equal to 10 b equal to 20 we compare b with 20 if it is true we assign a to 30 if it is false we go straight. So, either way we come here we assign d equal to a. So, d can take the value either 30 or 10 then we stop, but if you look at it more carefully because b is a constant the check b equal to 20 actually happens to be true. So, this no part will never be executed. So, we actually go here a is always 30. So, we can assign the value of d as 30 and then stop the s s a form is this. So, we have a 1 equal to 10 b need not be renamed because there is only one version of b then b equal to 20 is checked. So, we have another version of a here. So, a 2 equal to 30 and since we have two incoming works we have a 5 with a 2 and a 1 and that is assigned a 3 now we have d equal to a 3. So, these dashed edges are the s s a edges. So, here we have b equal to 20. So, there is a change in the value of b. So, it is propagating to the next use of b. So, that is only this node here we have a 1 equal to 10. So, that value is used here. So, it is actually there is an edge to this node here we have d equal to a 3. So, this is value is defined here. So, the edge actually links to this node etcetera. Similarly, this a 2 is 30. So, it is linked to this node because a 2 is used here. So, these are the definition use edges or the s s a edges. So, example this let us look at the trace of this example. So, we start here execute. So, this edge goes on to the flow pile and then when we look at that edge we take this node and interpret a 1. So, that becomes a 1 equal to 10. So, the constant value is retained then you know it has a single successor. So, this edge will be added to the flow pile. So, we traverse this edge come to this node. So, b equal to 20 will be added to the flow pile in the meanwhile this s s a edge is added to the s s a pile. So, we come to this stage where we pick up this node execute it. So, b is treated as a constant it is value is stored. So, this edge is now added to the flow pile right. So, that brings us to this node now because of b equal to 20 this will be added to the s s a pile. So, b equal to 20 is compared right. So, since the value of b is known to be a constant we can evaluate this constant as this expression as true. So, this becomes true and therefore, we need to add only the true edge to the flow pile and the no edge false edge need not be added to the flow pile. So, in this case we have this definition a 2 equal to 30 which is stored. So, a 2 is now taken as a constant. So, then that activates this edge and we go to this node. In this node we have a 3 equal to 5 of a 2 because we consider only those incoming edges which are marked as executable. So, only this edge is marked as executable this edge is not marked as executable. So, we consider only this particular predecessor in 5 there is only one predecessor. So, value of a 2 is what is given to a 3. So, it can be actually evaluated as a statement a 3 equal to 30 directly. So, now we mark this edge as executable because this is the only outgoing edge from this node. So, we go to this particular node this has the value rather the statement d equal to a 3. Now, a 3 can be determined as a constant from the previous interpretation. So, that would be assigned to d. So, after all this we have the structure where a 1 equal to 10 b equal to 20 this is a true this is a 2 equal to 30 a 3 equal to 30 then d equal to 30. In this particular example the SSC I just have no use because the we have come to the end of the graph. So, flow graph adjust rather the SSC I just become useful only later. So, now we can now perform a few more optimizations on this. For example, we have a 1 equal to 10 here and then we have a 2 equal to 30 here both of which have no influence at all on d. So, this is really dead code. So, a 3 equal to 30 is the only one which is relevant to us. So, d equal to 30 again. So, if a 3 is not used later on we can actually delete a 1 then a 2 and a 3 all these we can we need to retain only the. Similarly, if b is not used then this node can also be deleted the true node has no value now. So, we really have only d equal to 30 from now on if none of them other variables are used otherwise we would have b equal to 20 as 1 statement and then a 3 equal to 30 as another statement and finally, d equal to 30 as the third statement. So, we would have these values these are the only nodes which are relevant in this particular example. This example I am going to explain the example and then we will discuss the details in the next part of the lecture. So, here we have a loop as well. So, this is the loop structure and we have many merge functions one for b 2 here another for c 2 here and then we have one for b 4 here another for c 4. So, in this example there are going to be s s a edges which are going to be useful to make the flow of value to the various basic blocks quite visible and make the processing much faster. So, we will consider this in detail in the next part of the lecture. So, we will stop here. Thank you.