 Welcome to part 4 of the lecture on run time environments, today we will continue with garbage collection and you know see how exactly two types of garbage collectors work and so on. So, to do a bit of recap let us see what exactly are the problems with manual deallocation that is using free etcetera. So, there can be memory leaks which are not detected in the program it is very hard to debug such memory leaks. So, memory leak is you know failing to delete data that cannot be referenced. So, basically we have forgotten to delete the nodes which we are not using anymore in a tree or in a graph etcetera and then there is a second problem of dangling pointer dereferencing. So, we have forgotten to you know remove the references to deleted data. So, this could be you know a program error which is very hard to debug as well the solution at least partial solution to these problems is automatic garbage collection. So, garbage collection is nothing, but reclamation of storage and the storage is not being used by the program. So, we want to free such locations and use them again it is possible to you know do this provided the types of objects are known to the garbage collector. The reason why we require the types of objects is that the sizes of these objects cannot be determined without knowing the type. Secondly it is possible that the pointers which are embedded inside the objects you know also refer to some you know runtime objects. And it is not possible to determine whether there are pointers inside an object without knowing the type of that particular object. So, java for example is you know is type safe because you can determine the types of objects either at compile time or at runtime c and c plus plus are not. So, the basic mechanism by which we claim garbage is using the concept of reachability of objects. So, what we really want is to determine the root set which is nothing, but the set of all data that can be accessed directly by a program without having to dereference any pointer. That is if you consider the pointer variables which have been declared by the programmer at the highest level in the program. We are looking at these pointers as pointer variables as the root set. So, from that point you know from that set we reach the objects you know using one level of dereferencing. And we also look at the objects which can be reached from the at the second level and so on and so forth. So, let us see how this reachability can be used in garbage collection. So, it is also possible I forgot to mention that parameter passing assignments can propagate reachability. And of course, assignments and ends of procedures can terminate reachability of these objects. Similarly, an object that becomes unreachable can cause more objects to become reachable unreachable. So, that is you know if you have a pointer you can reach an object through it and if that object has more pointers in it you can reach more objects through it etcetera. Similarly, if you actually free an object which was being you know reached by a pointer. Now, the objects which were reached from the pointers within the object also become unreachable through this pointer. So, this is what we mean by the propagation of unreachability. The garbage collector you know periodically finds all the unreachable objects using one of the two methods. First one says we know that the objects change state. So, when the objects become unreachable at that transition from reachable to unreachable we catch the you know object and say now let me free it. So, second is periodically we scan the entire heap of the program and then locate the reachable objects and whatever is left over is the unreachable set. The first method that is the reference counting garbage collector is an approximation to the approach that I know the first approach that is we catch the objects which become unreachable at the time of transition. So, how do we do this basically we maintain a count of the references to an object. So, and when the count becomes 0 the object becomes unreachable, but how do we maintain the count. So, whenever you know the program performs actions and that changes the reachability of that object we modify the count as well. I will give you more details of this very soon and the reference count requires an extra field in the object. So, we maintain it you know using these rules. So, now the rules for maintaining reference counts there is a let us say there is a new object allocation. So, that means the object comes into you know become is born right. So, using new or any other mechanism. So, we set the reference count as 1 for this new object. So, that is quite fair because we have created a new object and that is being pointed 2 by a pointer. So, the reference count is 1 at that time parameter passing. So, let us say objects are being passed into a procedure as parameters. So, that means from now on the parameter also refers to the object apart from the original number of references to that object. So, it is only fair that we do an increment operation for each object on the reference count counter for each object passed into a procedure. So, this is a parameter passing and its effect on the reference count then there are reference assignments. So, that is u and v are references. So, and we are saying u equal to v this is an assignment. So, u is pointing to some object v is also pointing to some other object and from now on you will point to the object that v is pointing to. So, this is a pointer copy operation. So, obviously for the object which is pointed to by v that is star v the reference count has to be incremented by 1 because now from now on you will also point to it. And now you know you has kind of sees to point to the old object. So, reference count is reduced for the object which is pointed to by u that is star u. So, this is how the reference assignment propagates you know reachability and then of course, it ends the reachability for the objects pointed to by u as well. What about procedure returns? So, we know that the local variables die once a procedure returns. So, we do a reference count minus minus that is decrement operation on reference counts for each object which is pointed to by the local variables local pointers. Then there is transitive loss of reachability as well. So, whenever an object gets its reference count to 0 we must now trace further we must look at the reference count of each object pointed to you know by a reference within the object. So, we have an object there are more pointers within it those pointers will also be pointing to some objects. So, if an object to gets its reference count to 0 then you know we must decrement the reference count of the objects pointed to by the pointers inside as well. So, this must be done transitively and it will go on until no more decrement operation can be performed in any reference count of any particular object. So, this is the reference counting you know method of garbage collection. So, basically we whenever we find that the reference count becomes 0 we return that object to the heap. So, garbage collection is in some sense incremental. So, overheads are distributed to the mutator as operations and are spread out throughout the lifetime of the mutator. So, we do not have you know a huge amount of time which is spent in garbage collection at a single point in time, but it is distributed throughout the you know programs operation. So, garbage is collected immediately and hence space usage is low and usage is also very efficient if we are not delaying you know the collection of garbage. So, for real time systems and interactive applications such garbage collection is very useful because long and sudden passes are unacceptable in such a environment. So, if we take away too much time during the applications run to perform garbage collection it would obviously introduce long and sudden passes. So, to avoid this incremental garbage collection using reference counting is a very useful mechanism, but then these are the advantages. So, then there are also a few disadvantages there is a very high overhead due to reference maintenance because every time the reference changes we know that the reference counter has to be either incremented or decremented. And there is also a more serious problem the reference counting garbage collector cannot collect unreachable cyclic data structures such as circularly linkless. I will show you an example of this very soon since the reference counts never really become 0 this is something very serious. So, take this data structure. So, each of these is a four field object. So, this pointer inside this object points to this node and this points to this node. So, here is a cycle of references this pointer points to this node and then this pointer points to this node and again this pointer points to this node. So, here is another cycle and the second pointer here also points to this node. So, this object has a reference count of 1 because of this pointer pointing to it. This object has a reference count of 1 again because of this pointer pointing to it. This This object has reference count 2, because there are 2 objects 2 pointers pointing to it and then this object also has a reference count of 2, because there are 2 objects pointing to it this and this. So, what it so happens that none of the objects here can be reached through any other useful program variable pointer variable or any other variable any other pointer inside the program. So, this is basically garbage none of these nodes can be reached from anywhere in the program, but unfortunately the reference counts have not become 0. Therefore, none of these nodes will be returned to the storage pool the heap. So, this is a serious issue none of these nodes are in the root set either or transitively even in the root set. So, this storage will be permanently claimed and it remains in the program. So, if such circular data structures keep accumulating then even though they are all useless it will not be possible to return them to the storage pool. So, this is a big disadvantage as far as the reference counting garbage collector is concerned. So, how do we get rid of this problem. So, let us look at different type of garbage collector mark and sweep garbage collector. So, the where to briefly again the memory recycling steps in such a garbage collector are the program runs it requires memory locations. The garbage collector traces and finds reachable objects then the garbage collector reclaim storage from unreachable objects. So, the entire heap memory is scanned by the garbage collector in both these passes finding reachable objects and claiming unreachable objects. So, as the name indicates there is a marking phase to mark the reachable objects and there is a sweeping phase to reclaim the unreachable objects and return them to the storage pool. The advantage of this is it can claim unreachable data structures such as circularly linked list and so on. And the other feature is the in the nature of the algorithm it is a stop the world algorithm in other words when the program runs out of memory the program you know calls the garbage collector when the when an allocation is made the allocation fails. So, the garbage collector is called to perform garbage collection the garbage collector does these two operations of marking and sweeping and this may and during this time the rest of the program does not run. So, it this is called as a stop the world algorithm and this is the one which may introduce pauses in the program and it hurts real time and interactive programs. So, let us look at the details of the algorithm the algorithm basically is quite simple there are many variations of the marking sweep algorithm. We are going to look at one variety the simplest one and rest can be read from the text books. So, what is the marking phase start scanning from the root set first step in the marking phase is start scanning from the root set mark all the reachable objects that is set reachable be reached bit equal to 1 and place them on the list called unscanned. So, the root set is as usual the set of pointer variables which are declared by the programmer. So, all these all the objects which are reached from the root set will be marked as marked with reached bit equal to 1 and they will be placed on a list called unscanned. Now, we must do the marking recursion or rather progressively. So, there is a loop which continues until unscanned does not contain any object. So, unscanned while unscanned not equal to 5 do. So, take out an object from the unscanned list. So, object o equal to delete unscanned and then for each object o 1 referenced in o. So, there are pointers within the object. So, each one of these pointers is now actually traced. So, if the object is already you know is not yet reached. So, reached bit o 1 equal to 0 then we say the we make it 1 reached bit o 1 equal to 1 and place o 1 on unscanned. So, this goes on and on. So, once all the pointers in the object o you know object o are completed we take the next object from the unscanned list and continue. So, in this process every one of the objects which actually can be reached will be reached and each of these objects will have their reached bit set to 1. All the you know objects which have reached bit equal to 0 will be unreachable and they can be claimed during the next phase. So, here is an example to show how mark works. So, this is the root set the blue variety. So, these are the two variables programmer variables which used to access these data structures and this is the storage which cannot be reached. It is garbage and it needs to be collected. So, we start from the root set and then mark the objects these two objects as 1. And then from these two objects we progressively go to these and these and so on. So, we mark these two as 1 and these two again as 1. Then in the next step we go to these two objects and mark them as 1. So, we now have all the reachable objects marked as 1 and the unreachable object remains with reached bit equal to 0. The unscanned list now is empty. So, the marking phase ends. The next phase is the sweep phase. So, again the entire heap is scanned here. So, sweep phase each object in the heap is inspected exactly once not more than once. We will see why it is incorrect to look at it more than once. So, to begin with the free list is 5. We had nothing on it and we look at every object in the heap and if the reached bit is of that object is 0, we just dispose it of add it to the free list. If the reached bit was 1, then that means the object was useful. So, we set it to 0. So, you see for the objects which had the reached bit equal to 1, we have now reset it to 0. So, looking at the object again would be incorrect because it would send all these objects to the free space. These were really useful objects. So, we should not be looking at them again. So, let us see how it works in this case. So, up to this was the marking phase. So, all the reachable objects have been marked. These are the unreachable ones. So, again as we inspect every one of the objects here, we take this, take this and take this, return it to the storage pool and that ends the garbage collector operation. So, this gives you an overview of garbage collection. There are obviously many varieties of garbage collectors which are in operation. In the literature, the concurrent garbage collectors, copy garbage collectors etcetera are all described. They are all useful in different circumstances. My aim was to give you an overview of garbage collection. So, with this the runtime system lecture ends and we will continue with local optimizations. So, welcome to part 1 of the lecture on control flow graph and local optimizations. So, you know in the past lectures, we have seen different phases of the compiler. For example, to begin with we saw lexical analysis, then we saw syntax analysis, then we discussed semantic analysis and after that we discussed you know intermediate code generation and of course, we also saw the runtime support that is necessary. Now, before we generate machine code, I mentioned in the early lectures that there is something called machine independent optimization which needs to be performed. So, in this lecture, we are going to see one type of machine independent code optimization called as the local optimization and the other types of optimization will be discussed later. So, in this lecture we will see the reasons what rather the definition of code optimization, its purpose and then the types of optimizations. We are going to study what are basic blocks, what are control flow graphs, the different types of local optimizations, the procedure for building a control flow graph and then of course, methods of storing basic blocks efficiently using you know value numbering directed acyclic graphs etcetera. So, what exactly is machine independent code optimization and why is it necessary. So, the intermediate we have discussed intermediate code generation in great detail and we know that it is a fairly straightforward process, it introduces a large number of inefficiencies into the intermediate code that is generated. For example, whenever there is an expression a equal to b plus c, you know we would first do t 1 equal to b plus c and then do a equal to t 1. So, now we have really introduced an extra copy of a in the temporary t 1. So, this is what I mean by extra copies of variables and using variables instead of constants. So, well there are many constants that we would like to use in the program, there is no straightforward way of using the may not be a straightforward way of using the constants in some cases. So, the intermediate code generator would generate an assignment statement to assign a constant to a variable and then use that variable instead of the constant itself. Then there are repeated evaluation of expressions. So, even though an expression such as a star b has not changed its value, we may be evaluating a star b all over again within a short period of time instead of trying to reuse the value which was computed earlier. So, these are all things which happen you know a famous example of this repeated evaluation is that i star 4 in the case of a of i plus b of i etcetera etcetera. So, these are the sources of inefficiency in the intermediate code, the reason we did not bother about such inefficiencies is that we know code optimization would remove such inefficiencies and make the code better. Of course, improvement of the code may be this are only a minor examples, there are many more examples which we will see in the future, the improvement may be in time in space or in power consumption. So, when we improve the time requirement which is probably what we do most of the time, we would be making the program run faster and faster. When do we require improvement in space? In other words, we would like the program to take as little space as possible. So, in embedded systems memory is actually very expensive to incorporate. So, we may have to pack all our programs into the into a very small memory and in such cases memory based optimizations to reduce the space requirements becomes very important. The same is true in embedded systems and sensor network systems and so on, power consumption it becomes a very important factor in such cases. So, the embedded systems and sensor networks you know they run on small size batteries. So, it is necessary that the battery can be used over a large period of time. To do this the power consumption of the program has to be minimized. If we simply make the program run faster of course, the power consumption will reduce because the program is now running for a shorter period of time, but there may be very subtle optimizations possible you know by compilers such as reducing the voltage of the chip, switching of certain function units etcetera. So, the compiler may be able to perform you know optimizations keeping these features in mind. So, power consumption reduction is another type of optimization which is sometimes possible. So, the in general optimizations change the structure of programs. In fact, sometimes beyond recognition for example, optimizers in line functions you know. So, the function call disappears the body of the function replaces the call it unrolls loops. So, instead of running the loop you know 300,000 times it can possibly unroll the loop for three times and then say now I will run it only 100,000 number of times. So, why should it do such things the unrolling of loops etcetera will increase the opportunities available for instruction scheduling etcetera and it may actually increase the parallelism in the program. So, the same is true for inlining of functions. So, these are all desirable optimizations. It may also eliminate some of the programmer defined variables. So, for example, in induction variable elimination the variable which is defined as an induction variable may eventually get eliminated from the program. The effect of all this would be that we cannot use a debugger on optimized programs because the program has now changed and the mapping to the source code is no more possible. So, debugging an optimized program may not give out appropriate hints to the programmer at all. So, in GCC for example, kind of stops all the optimizations if you choose the debugger option. Code optimization in general consists of a bunch of heuristics. So, in other words the percentage improvement is not guaranteed by any optimizer. The simplest example would be you know if there is a set of statements which cannot be improved further then applying any number of heuristics on it will not improve the program. So, in such a case the code optimizer cannot do anything. So, you cannot guarantee the amount of improvement that a code optimizer provides. So, sometimes it may be 0 as well. Optimizations may be very broadly classified as local optimizations and global optimizations. So, let me give you a few examples of these optimizations. All local optimizations are within what are known as basic blocks. We will know what these are in a very short period of time. To be very brief basic blocks are single entry single exit areas of code and in such basic blocks whatever optimization we perform the effect will not go beyond the basic block. That is why they are called local optimizations whereas the other type of optimizations are global optimizations. So, the effect of the optimization is on the whole procedure or even the whole program. Some of the optimizations the important ones are local commons of expression elimination then dead code elimination. So, what is dead code instructions that computer value that is never used that is really dead code elimination of such dead code and reordering computations using algebraic laws such as commutativity associativity etcetera. So, these are all called as local optimizations. We are going to see in this lecture how to perform such local optimizations. There are the some of the very important global optimizations are listed here. The global common sub expression elimination which is similar to in effect as the local commons of expression elimination then there is constant propagation constant folding loop invariant code motion partial redundancy elimination loop unrolling function inlining vectorization concurrentization. There are a host of others as well more than another dozen important optimizations which are possible and GCC performs quite a few of them. So, we will you know perform we will understand how to perform local optimizations in this lecture and then in the later lectures we will see how to perform global optimizations as well. So, as I said local optimizations are all performed on basic blocks. So, we must understand the definition of basic blocks how to construct basic blocks etcetera before we go to optimizations. So, basic blocks are sequences of intermediate code with a single entry and a single as the. So, let me show you an example before we consider further before we go further. So, for example, this entire piece of code is one basic block. So, you can see that there is a single entry here and there is a single exit at this point as well. Similarly, these two statements together form a single basic block. So, here is the entry to the whole procedure and here is the exit from the basic block. So, but at the same time this piece of code which corresponds to the entire high level language program which is written here. So, this is the simple dot product program is not a basic block why. So, as we go on here is an entry then we go down and then there is a go to which actually re enters the program I know. So, the B 2 part is the beginning of this T 1. So, here is the B 2 part. So, this is re entering the program again at this point. So, this is not a basic block this whole thing has to be still carved into basic blocks. So, we consider the quadruple version of intermediate code here. We already saw that in the picture to make explanations easier and control flow graphs show control flow among basic blocks. So, this is actually a control flow graph. So, control flow between this and this block is shown by this arc this and this block is shown by this arc and this is a loop again goes back to the beginning of the same block. So, these are all arcs in the control flow graphs and the entire structure is called as a control flow graph. Basic blocks are represented as directed acyclic graphs. So, they do not have any loops within them. So, they are really directed acyclic graphs and we will see how to represent them using value numbering method applied on quadruples and we will also see how to perform the local optimizations on basic blocks. So, let us see how to carve out basic blocks from a piece of intermediate code. So, we must determine what are known as the set of leaders that is the set of first statements of the basic blocks. And once we actually get all the leaders in the program you know getting basic blocks is very easy a leader and all statements which follow it up to but not including the next leader or the end of the procedure is the corresponding block basic block corresponding to that leader. So, that is very easy. So, the basic idea is to form leaders and any statements not placed in a block can never be executed and can be removed. So, this is the dead code elimination that we perform. So, if code does not get into a block then it can be it is unreachable and it can be removed. Now, how to get the leaders? The first statement of the program is obviously a leader. So, you must begin somewhere. So, this is the first statement then any statement which is the target of a conditional or unconditional go to is a leader. So, please understand this carefully. We are not talking about the conditional go to statement per say, but we are talking about the target of the conditional statement. So, the target is an entry point to the basic blocks. So, that is why it is a leader and any statement which immediately follows a conditional go to is a leader. So, let me show you how to carve out these basic blocks from this procedure. So, the first statement is a leader. So, this particular thing you know prod equal to 0 is a leader. Then we continue scanning we come here. If i less than equal to 20 b 2 b 2 of course, begins with t 1. So, that is this. So, this statement t 1 equal to 4 star i is a leader. So, we have said prod equal to 0 is a leader t 1 equal to 4 star i is another leader. Then here is this is a conditional go to statement. The statement which follows it is also a leader. So, stop is also a leader. So, we have formed three leaders. Why is it we say the statement following a conditional go to is a leader and not an unconditional go to? The reason is very simple a conditional go to has two exits. One is going back of course, in this case and the other one is if the value of i is greater than 20 the code falls through and executes stop. So, this is the other exit. So, control can go either backwards or forwards that is why the next statement after the conditional go to is an executable statement and that can be marked as a leader of course. And of course, since the entry is the target target is nothing but another entry into the program this will also be called as a leader. Now, suppose this were to be just go to b 2 instead of if i less than equal to 20 go to b 2 suppose this statement was just go to b 2. Then if you observe we will actually go into an infinite loop and we will never be able to reach this statement stop. So, we should never mark this stop as a leader you know this is really dead code and it will get eliminated. Once we have formed the set of leaders. So, this is a leader then this T 1 is a leader and stop is a leader. So, from prod to T 1. So, not inclusive is the first basic block and from T 1 up to stop not inclusive is the second basic block and from stop till the end of the program which is stop itself is the third basic block. So, this is how basic blocks are called out of a program. Now, to do a little of little bit of work on these basic blocks we must also add the arcs into between these basic blocks in order to form the control flow graph. So, how do we add these arcs of course, the first thing is we look at the you know look at the basic block look at the last statement in the basic block. Then if it is not a you know unconditional go to statement then the statement following it can be actually taken as the target of this particular arc. So, in this case we have i equal to 1 which is not a go to and therefore, we mark you know we place an arc between these two to indicate the flow of control. Similarly, we go to the last statement of this particular block it is not an unconditional go to if it were then this would have been decode. So, this is conditional go to. So, the next statement will be executed if the condition is false. So, we place an arc from here to here indicating that control flow can happen here as well. Then of course, we have this arc to show that this is the target of this conditional go to. So, we place the arc between this point the end of the basic block to the target b 2. So, this is how we place arcs to form a control flow graph. So, this is what I just now explain the nodes of the control flow graph or basic blocks one node is distinguished as the initial node. So, this is the procedure entry there is a directed edge b 1 to b 2 if b 2 can immediately follow b 1 in some execution sequence. So, this is what I just explain that is there is a conditional or unconditional jump from the last statement of b 1 to the first statement of b 2. So, here from the last statement of this to the first statement of this and this of course, is not at all a go to. So, from here to here b 2 immediately follows b 1 in the order of the program and b 1 does not end in an unconditional jump. So, this is the other case. Now, how do we represent a basic block? We represent it as a record the record has a count of the number of quadruples in the block a pointer to the leader of the block then we need the predecessors and successors of the block as well. So, with this we can actually form the control flow graph as well. So, the jumps to points to the basic blocks and not quadruples and this makes code movement easy. So, let me explain why this is. So, just imagine that this statement if says if i less than equal to 20 go to 3 because this is statement number 3 really 1 2 and 3. Suppose, we really you know add a few more statements here some statements from here may move to this statement this particular basic block during some optimization. In such a case what happens is the number of this particular quadruple the first quadruple in the basic block b 2 its number will change it may become 5 or 6 let us say. So, if we had maintained go to 3 here we would have had to change that as well you know this number will have to be changed to 5 or 6 appropriately. Whereas, if we simply say go to b 2 where b 2 is nothing but a pointer to the basic block record then we know that in spite of the changes made to the number of quadruples in this basic block this pointer does not change. So, there is really no harm no change that is necessary. So, this makes code movement easy. So, we do not have to worry about changing any numbers if we move code from here to here or to some other place. So, now let us understand how to represent the basic block using directed acyclic graphs. So, what I said here you know this is the extra statement corresponding to a basic block we still have all the quadruples in the basic block and we must actually represent them using directed acyclic graphs this is meta information corresponding to a basic block. Here is a basic block there are 10 statements in this basic block. So, I have observed that there are no jumps here it is just one block of statements. So, let us understand what this structure is this is obviously a directed acyclic graph there is no loop here and let us understand how to build this dag from this set of quadruples. So, the procedure is what I am going to describe now. So, here is a quadruple a equal to 10. So, we form a node a a node containing the value 10 and we attach a label to it called a and then there is an assignment statement b equal to 4 star a. Now, we know we search the dag how to do the search we will see later we search the dag find that there is a node with label a in it. So, this node b can be I know this star node can be created with left child as 4 and the right child as this a and we attach a label b to it. So, we have formed this computation tree as far as these two statements are concerned third one says t 1 equal to i star j. So, similarly we have i here we have j here we create the new nodes i and j then we create a new node star attach these two arcs and attach a label t 1 to it. So, the notation that I have used is the first time that we create a node the value is put inside whether it is a variable or a constant and any extra variables are attached as labels then we have c equal to t 1 plus b. So, we must form actually a node called plus and label it as c. So, this node plus will have t 1 as it is left child and it will have a b as it is right child. So, we can search the dag find out the appropriate nodes corresponding to b and t 1 and establish these links. Then we have t 2 equal to 15 star a. So, we have t 2 here. So, 15 is a new node that we want to create a is already present. So, we can create a star node and attach the links and label it as t 2 then we have d equal to t 2 star c which is very similar. So, we search for t 2 we find it then we search for c we find it and then we form the star node and label it as d. We have e equal to i we find i. So, we do not have to really do any extra creation of node we just attach a label e to it. Then we find t 3 equal to e star j. So, e is here already available then you know t 3 is this. So, e and i are the same j is also here already. So, we are just simply do not create another node for t 3 we attach it to the star node. So, this has two labels now t 1 and t 3. Then the next statement is t 4 equal to i star a. So, we have t 4 here this is a new node that star is a new node that needs to be created i is already there a is also already there. So, we create a new star node and attach a label to it as t 4 the last one is important it says c equal to t 3 plus t 4. So, we find you know t 3 already available and we also find t 4 which is available here, but the point is the variable c is being reassigned. So, there was already a c variable here. So, we need to actually kill the occurrence of the old variable it is not relevant anymore create a new node plus attach a label c to it. So, what are the advantages of this particular approach well if you observe carefully you know we never created the node for t 3 all over again. So, t 1 corresponds to i star j and t 3 even though it is written as e star j because of the assignment equal to i it is really i star j. That means the expression i star j is now both t 1 is available in t 1 and t 3 you know we would have recomputed i star j 2 times if we had used this code. Whereas, if we use the dagger representation we are not going to recomputed i star j all over again it is available in t 1. So, we will simply use t 1 in place of t 3 in our entire program. So, this is an example of common sub expression elimination. Then the second advantage for example, here we have b equal to 4 star a. So, we of course, have created a node for star for the example I showed that we established links to 4 and 10 here a is 10 of course. And instead of doing that when we search for a since we get a value 10 and 4 is actually a constant as it is we could have performed this multiplication 4 into 10 as 40 replaced this entire structure by having one you know node b with the value of 40 this entire computation could have been eliminated. So, this is known as constant folding the we have propagated the value of a from here to here. So, that is constant propagation then we can evaluate 4 star 10 as 40. So, that would be constant folding. So, there is a possibility of constant propagation constant folding and also common sub expression elimination if we use the directed acyclic graph representation here. The only difficulty is if we really build a directed acyclic graph using the pointers and links as we have seen as we see in this picture this is the way we build trees you see. So, if we build such a structure physically by establishing these links and nodes and so on and so forth it is not of much use really let me explain why. So, at some point we want to search for you know say b or something like that right. So, we want to search for or a even better there is no simple mechanism to search for a we will have to start at the root of the directed acyclic graph search the possibly the entire DAG systematically and then at some point in the search we may we will end up getting this a. So, in the worst case you will search the entire DAG this is a you know very expensive process if we had some 1000 statements in the basic block then you know searching the entire DAG of 1000 nodes is very time consuming. So, construction you know of the DAG and trying to find common sub expressions or nodes in the DAG by exhaustive search of the directed acyclic graph is a time waste you know is a time consuming procedure and is a waste of time. If we use links and tree like structures like this this is the only way whereas, if we avoid using these links represent this entire directed acyclic graph using hash table structures then you know the entire operation becomes much much faster. So, this process is known as value numbering and it is used in the construction of basic blocks. So, a simple way to represent DAGs is via value numbering and value numbering uses hash tables. So, searching for expressions and variables is very quick very time efficient the basic idea is to assign numbers to expressions and these numbers are called value numbers. So, when we assign such numbers we when we want to check whether two expressions are the same we simply compare their value numbers if the value numbers are the same then the two expressions are equivalent they will produce the value at all times. So, this is the idea behind value numbering again we are going to assume quadruples with binary or unary operators the algorithm will use different types of tables. For example, for expressions it uses hash tables and then for variables it uses Valnum tables and for constants it uses name tables. So, these you know value numbering methods can be used to eliminate common sub expressions do constant folding then they can be used to perform constant propagation in basic blocks and they can of course be used to extra you know take advantage of the commutativity of operators addition of 0 multiplication of 1 and so on and so forth. So, at this point we will stop the lecture and continue with details in the next lecture. Thank you.