 Welcome to part 2 of machine code generation. So, in the part 1, part 1 of the lecture, we considered the main issues in machine code generation and I also gave you a sample of generated code. So, today we will continue looking at the samples and other material. So, this is the sample code that we saw last time. There is a function f 1 which has action code segment 1 and then I call to another function f 2, another piece of code segment 2 and then halt whereas, function f 2 has just a piece of code and then it returns. The activation record for both the functions f 1 and f 2 are shown here. So, the activation record takes 48 bytes for f 1 and 76 bytes for f 2 because, we do not have any jump subroutine instruction, we really store the return address and then you know jump to the subroutine itself. So, because of that we require a slot in the activation record of the callee to store the return address as well. Here is the code that is generated by the machine code for function f 1. So, the code for action code segment 1 you know so that is here. Now, it is time to call the function f 2. So, to begin with we store the return address. So, remember there is no need to you know allocate activation records it is static allocation. So, the activation records have already been set up in memory and they are permanent. So, the activation record for f 1 spans the addresses 600 to 647 and the activation record for f 2 spans the addresses from 648 to 723. So, this is just a replica of this picture return address data da variable x variable y. So, you can see that and similarly this as well. So, before we jump to the subroutine the return address, which is this particular address of action code segment 2 that is moved on actually it is moved into location 648, which is the slot to store the return address in the activation record of f 2. Then the parameter is actually pushed or moved into location 652. So, that is corresponding to parameter 1 here and then we execute a simple jump instruction. So, when we go to function f 2 we execute the code and now use the return address, which is given on the activation record at location 648. So, indirect jump will take us back to this particular address 264. So, here we execute action code segment 2 and then halt. So, this is the scheme when we do not have any jump subroutine instruction and it is static allocation. So, let us see what happens with jump subroutine instruction. The address code is 3 address code is the same it is just that this activation record does not have a slot for return address here. So, the number of bytes required is 44 here and 72 here what about the code itself the machine code now is slightly simpler. So, this would be you know action code segment 1 then. So, here suppose we and I have also changed a little bit in the f 2. So, there is no parameter either just to simplify the code. So, here we execute action code segment 1 and now since the return address is automatically stored on the hardware stack when we execute the jump subroutine instruction there is no reason to store it. So, we execute j s r 400 which automatically stores the address 248 on the hardware stack. So, we go to this particular address start executing it and then we say return it automatically picks up the return address from the stack and returns to this particular address 248. Activation record format remains the same except that there is no space for the return address otherwise there is not much change. Now, the other variety is dynamic allocation and no jump subroutine instruction. So, there is code for function f 1 then action code segment 1 call f 2 action code segment 2 and return and f 2 has action code segment 3 then there is a recursive call to f 1 action code segment 4 another recursive call to f 2 itself and then action code segment 5 and return. So, the return address is stored here because we do not have a jump subroutine instruction and there is local data and other information all the other temporaries etcetera stored here. So, we require 68 bytes here and 96 bytes here. So, there is also a parameter in the function f 2. Now, we need to set up activation records when we call functions. So, we must initialize the stack pointer say some value 800 that is where the stack may begin. So, let us move that into the stack pointer. So, that it is a valid address. So, this is the address at which the stack begins. Now, the code for f 1 it has code for the action code segment 1. Now, this part is the allocation of the activation record add 96 to sp. So, that means, we increment the stack pointer by 96. So, thereby allocating you know 96 bytes for the activation record of f 2. So, now, we move 258 which is the return address to the first location on the stack pointer move the parameter to the next location and then execute a jump. So, except for this part the rest of it is the same the of course. So, there is a minor difference we use the stack pointer with indirect addressing in order to access the information on the activation record or put something into the activation record. So, all the information is accessed through sp. So, that is why the indirect addressing scheme is required. Once we jump to 300 that is the code for f 2. So, this has code for the action code segment 3. Now, there is a recursive call. So, the size of the activation record here the size of the activation record for f 2 was 96 the size of the activation record here for f 1 is 68. So, 68 is added to sp thereby allocating the activation record for f 1 on the stack. The rest of it is similar the return address and there is no parameter. So, jump to 200. So, the again you know the action would come to this point when the code executes. So, once the code completes it the there is a return using the sp. So, indirect sp. So, that is precisely what brings us to you know the return point. So, that is 258 right. So, that is it this is the point. So, we when we execute sorry not this point here. So, from here we the return address would be 364. So, 364 is the place where we return after we call to f 1 is completed. So, at this point we must deallocate the activation record. So, subtract 68 from sp. So, that brings us back to the original position. Now, the code for segment 4 is you know present then there is another recursive call. So, please see that there is a recursive call to f 2 again. So, again the stack allocation of the activation record happens return address is pushed on to the stack. The parameter is also pushed on to the activation record and a jump to 300 that is this code itself happens. So, this is how the recursive call you know takes place. So, the point I am trying to make is that there is no difference between the recursive call and non recursive call. The activation records are created in both cases dynamically. Rest of it is simple you know whenever there is a return from the subroutine it comes back and you know and so on and so forth. Finally, dynamic allocation with jump subroutine instruction the only difference is there is no return you know slot there is no slot to store the return second address. Rest of it is the same. So, we have to allocate the activation record now the size is slightly less and then we jump subroutine to 290 that is the address of f 2. So, this implies the return address is stored on the hardware stack and then the control goes here. So, even for the you know for example, the recursive call to f 1 it just allocates the activation record and jumps to 200 using the J S R instruction. Similarly, when we have a recursive call to f 2 we again allocate another activation record move the parameter and then jump to the beginning of f 2 it is using the J S R instruction. So, J S R return automatically takes care of you know returning to this particular address following the place where we called because the return address is stored on the activation in the hardware stack. So, that is about the instructions which are really going to be generated for various types of intermediate code. So, now let us begin our discussion on the code generator itself. What is the design that we adopt for a code generator and there are at least two simple code generators that we are going to discuss. The scheme A for example, it treats each quadruple as a macro. So, if you take the quadruple A equal to B plus C as a generic quadruple plus need not stand for the plus operator it could be plus minus star slash any one of these. So, the code generated would be either this or this. So, the point is every time we want to do an operation on an operand we will load it into a register do the operation store it back into that memory location. So, that no registers are resumed to be in use in any particular translation scheme. So, here load A equal to B you know A equal to B plus is A load B into R 1 load C into R 2 add R 2 R 1 and store R 1 to A. So, after the code is executed both R 1 and R 2 are free. Similarly, if you use this scheme load B comma R 1 add C comma R 1 and store R 1 comma A the register R 1 will be free after the entire sequence is executed. So, the advantage of doing this is we do not have to perform any register location and we can simply write a macro for each one of the quadruples the macro will be expanded by and whenever we instead of you know the code for A equal to B plus C will be just that call to the macro corresponding to the quadruple. So, the final output would be a sequence of macro calls it should be expanded by the macro assembler and the machine code would be generated. The difficulty here is inefficient code repeated you know load store of registers. So, every time since we load the value from the register to the location rather load the register from the location it implies load and again and again. So, we are not you reusing the value which is already present in a register in some other quadruple. So, this is very inefficient and but very simple to implement. So, if we are looking at a prototype compiler then the first cut code generator could be based on macro. So, and slowly gradually as the code is the compiler is debugged you know we could start designing a better code generator. Then scheme B scheme B is slightly more complicated. So, here we track the values which are present in registers and try to reuse them. So, the basic rule is if any off-rand is already in a register take advantage of it. So, this also makes the code generator a little more complicated. We require to track the value of the registers when and make sure that we know when it changes values and so on and so forth. For that purpose we use register descriptors and address descriptors. So, these are two data structures that we apply a register descriptor actually tracks register and variable name pairs. So, for each register it says which are the you know variables that contain the value at the same time you know. So, if a single variable can be present in different registers then that is also possible right. So, many registers can contain the value of a single variable or a single register can contain the values of multiple variables. We try to avoid the first situation that is many registers containing the same value of the same variable make by making sure that we use the same register whenever there is a need to use it. So, the other situation is what we need to worry about that is a single register containing values of multiple names you know if they are all copies. So, we need to track this and make sure that whenever that variable name occurs the same register is used. So, pairs of this kind are stored in the register descriptor for register and what are the variable names that it can correspond to. In some way the inverse of that would be with a variable name what are the locations corresponding to it. So, it is possible that a single variable name may have its value in multiple locations it will be definitely in memory, but it may also be in register and if it is a stack machine it may be on the stack as well. So, it is necessary to track such pairs as well and in the process of code generation these two data structures would be repeatedly updated. So, the basic principle is to leave the computed result in the register as long as possible. So, what does that mean. So, we try to use a different register whenever we need a an extra register and if there are no registers only then we see now it is time to empty a register and store its value back in the memory location corresponding to it. So, every variable has what is known as a home location corresponding to it. So, the home location will be updated whenever the register corresponding to it cannot be allowed to keep the value anymore. The other point when we need to empty a register would be the end of a basic block. So, I already mentioned that the register will have to be emptied when the register is needed for another computation, but at the end of the basic block also we need to empty the register. The reason is our code generation scheme is a basic block oriented scheme. It does not look at any other basic block when it is generating code for a particular basic block. So, at the end of the basic block it stores all the registers back into their home locations and then you know make sure that the register set is vacant empty for the next basic block. So, at the end of the basic block do we have to store every you know register into its home location. In other words do we have to generate instructions to store every register into its home location not necessarily. For example, we use the concept of liveness to help us in this in making this decision. We will discuss liveness in great detail later during data flow analysis, but for a basic block the definition is very simple. A variable is live at a point if it is used later and of course, if it is used in other basic blocks then also it is live, but we will not be bothered about such a usage because it requires data flow analysis to track such usage. But suppose we know that data flow analysis has been performed liveness analysis has also been performed. So, at the end of the basic block we know which variables are going to be used in other basic blocks and which variables may will not be used definitely in other basic blocks. If this information is known to us I am now talking about something which we have not yet discussed liveness is available by data flow analysis that is the assumption here. Then we can make a sophisticated decision. So, on exit from a basic block we need to store only those live variables that are not in their memory locations already. So, if you look at the address descriptor it will tell you where the variable has its value is it in the home location also or is it just in the register. If it is only in the register then you know then we need to generate an instruction to copy the register value to its home location, but if it is also in its home location then we do not have to generate such an instruction that is one. The second is what if the variable is not live at all if the variable is not live then it will not be used further. So, whether it is in the register or home location it really does not matter. So, we do not have to generate any instruction to store it back into their home location. If the liveness information is not known to us then we must assume that all variables are live at all times. So, this is the you know most pessimistic assumption which leads to bad code, but under certain circumstances we may have to live with this assumption. Let us take an example here is a quadruple a equal to b plus c as I said plus is a generic operator. If b and c are in registers in say in R 1 and R 2. So, b is in R 1 c is in R 2. So, now we can possibly generate an instruction add R 2 comma R 1 which brings the result into R 1 cost of course, is 1 because it uses only registers when is this legal. According to the scheme that we are using we need to keep the value of a variable in a register as long as possible. So, this scheme this instruction is legal only if b is not live after the statement. So, b is in R 1 and now we are over writing b rather R 1. So, the value available in R 1 is lost. If the value of R 1 is going to be used later we would possibly have not you know updated its home location. So, generating this instruction when R 1 b is still live rather then it would be incorrect this is legal only if b is not live. Otherwise if we are forced to empty the register R 1 we will have to generate an instruction to move the contents of R 1 to the home location of b and then use the register R 1. So, we will see that also a little later. So, this is you know that means we need to check where to generate the rather where to store the result. The second possibility is R 1 contains b, but c is in memory. Well we could simply add c comma R 1. So, generate this instruction cost is slightly higher than the previous one that is cost 2 that is understandable because c is in memory, but the result is in R 1. Again this is legal only if b is not live after the statement. So, that is another thing that we need to keep in mind. Third possibility is we load c into R 2 then add R 2 comma R 1. So, again the cost is even higher and the result is in R 1. Again this is live legal only if b is not live after the statement, but this could be attractive if the value of c is subsequently used. So, it can be now taken from R 2 itself. So, because we have loaded it into R 2. So, the examples were supposed to give you some idea of the complexity involved in code generation that is we need to check the address and register descriptors at various points in time. We also require some extra information called the next use information to make an informed decision about which register to use etcetera. So, next use information is used both in code generation and our local register location which we are going to discuss very soon. So, what is the definition of the next use information? So, the next use of a in quadruple i is j. So, in other words we have a quadruple i which defines a value for a. So, there is an assignment a equal to something and then we have I know several other quadruples before quadruple j which uses a. The most important point here is control flows from i to j, but there are no more assignments to a. So, the value of a is not changed then the value computed here is used in this quadruple. So, we say that next use of a in quadruple i is j. So, we have to compute the next use information we will see how to use it little later. Obviously, we need to check for this condition and then attach information about the next use of i to the quadruple next use of a to the quadruple i. So, in computing next use we assume that on exit from the basic block all temporaries are considered non-live. So, this is a correct decision because we do not reuse temporaries across basic blocks we generate new temporaries whenever necessary. All programmer defined variables and non-temporary variables are supposed to be live. So, we did not perform any lameness analysis data flow analysis and that is the reason why we are assuming this. So, then each procedure and our function call is supposed to start a new basic block. I already discussed this in the you know basic block discussion lecture on basic blocks. So, otherwise we have a problem of killing all the quadruples in the basic block. So, this is best you know sent to a different basic block. Next use is computed in a backwards scan on the quadruples in a basic block starting from the end. I will give you an example to explain the procedure and of course, the next use information is stored in the symbol table. So, for that particular variable the next use information is also attached. So, here is the example for computing next use. So, here this is a simple basic block it is the same dot product example. It is just that the quadruples some of them are in a different shape prod equal to prod T 6 instead of you know some temporary equal to prod plus T 6 and temporary equal to prod rather prod equal to temporary and similarly, here. So, this is slightly already improved version in other words local optimization has been applied and some code rewriting has also been done to take care of such possibilities. So, we start from here this quadruple it uses i right there is no writing into i it only reads i. So, along with the variable i in the symbol table we attach the information the first field says live or not live the second field says what is the last use of that variable and the third field says either next use and a number of the quadruple or says no next use. So, in this case the variable i is a programmer defined variable. So, it is live even after exit from the basic block the last use of the variable i is 11 self quadruple and then there is no next use of the variable because the basic block ends here. So, when then we go to the previous quadruple i equal to i plus 1. So, here i is used on the left hand side and also the right hand side. So, when we say i is live that means it must be used it is being assigned here and the assigned value must be used later it is indeed being used later. So, the variable is live this can be inferred very easily by looking at the symbol table for i we are updating it. So, i was actually live and of course, it will be live even now because it is a programmer defined variable and the last use was 11. So, if the last use information was 0 then this would have been the first definition of i, but the last use information says 11. So, i value which is defined here will be used in 11. So, that is why this is the variable is live the last use is 10. So, that is the self quadruple right hand side and then the next use is 11 that is this quadruple. So, we have updated the information of this. So, how did we say a new 11 that was picked up from a new 11 and copied into this place. Then this quadruple I will explain just this to show you the procedure prod equal to prod plus t 6. So, there are two variables prod and t 6 for prod it is live because it is a programmer defined variable which is defined here and then you know possibly use later. The last use of the variable is 9. So, that is the right hand side part which is here and there is no next use for the variable within the basic block because this is the last definition and it is not used again. The variable t 6 is a temporary. So, this is the first use of that variable and there is no next use again. So, this is not live and then from the you know when I say first use when we are in the scanning process it is not live because there are no more uses here. Last use is 9 that is this basic block and there is no next use of t 6 within the basic block itself. So, this continues. So, let us look at this quadruple at address 5 it is t 3 equal to t 2 bracket t 1 indexed assignment. So, t 3 is now you know is defined here right. So, then you know it is the liveness ends here right because it is being defined here and then the last use is 0 because it is defined here right and then the next use is 8 that is because t 3 is being used here. So, from this definition the usage is up to this point. So, that is the next use 8 sorry that is the next use 8 correct. So, we say last use is 0 because t 3 is defined here. So, the whatever value it had is destroyed whereas, in this case there was a usage here that is why the last use was set as 9. So, whenever it is a defined definition t c is equal to t 3 star t 5 l u will be 0. So, here also then here also and here also here as well here as well. The next use information tells you where that particular variable will be used. So, t 2 and t 1. So, t 2 is not live because you know it is not used anymore and then last use was 5. So, that is this itself and then there is no next use for t 2. Similarly, for t 1 it is not live last use was self quadruple, but there is a next use in quadruple 7. So, that is this use. So, this can be picked up from the symbol table for t 1 itself. So, the symbol table for t 1 here says last use 7. So, that would have been picked up and copied here. So, this is the way in which we compute the next use information by doing a backward traversal of the quadruple array. So, now we have the next use information already computed. Now, we are ready to discuss the algorithm for code generation. So, we as I already mentioned we deal with one basic block at a time and we assume that there is no global register location. There is a local register location which is called getreg. We are going to discuss that in a few more minutes. So, here is the code generation algorithm for each quadruple a equal to b of c do the following. First of all we need to find a location to perform the operation b of c say that is l, but how do we find l? This is a return by the function call getreg. It could be a register or it could be a memory location, but a preference is always given to a register. We will see details of getreg later. So, let us assume that a location either a register or a memory location has been returned by getreg and that is where the operation b of c will have to be performed. Now, where is b? b is in a place b prime. If we look up the address descriptor for b it will tell us where b is. To begin with when we generate code for the first quadruple in the basic block all these descriptors are empty. So, the variable b will not even be found anywhere you know it will be only in the home location that is the only thing we can say. So, always at the beginning all the variables will be in their home locations and all the registers would be empty, but as we go on generating code very soon we will see that some of the variables will be found in registers. So, we find the place b prime or the location b prime where b is present and obviously, we prefer the register for b prime if it is already available both in memory and register. Suppose b prime is not in l. So, l is the place where we perform b of c. So, we generate the instruction lower b prime comma l. So, it moves the contents of b prime to the location l and gets it ready to perform the operation of along with another variable c. The same thing will have to be done to c as well. So, we need to answer the question where is c we find the place c prime using the address descriptor for c and the same rule holds prefer a register if it is available. Now, we are ready to generate the instruction of c prime comma l. So, c prime is the place where c is present l is the place where b is present if it was not present we have moved it and now the location l will also have the result after the execution of this instruction. So, that is the translation which is generated for a equal to b of c, but after this we are still not you know done anything to the descriptors. So, the descriptors for l and a must be updated. So, if l was a register we have to update the register descriptor if l was a location we must update the address descriptor the same holds for a as well. So, remember we there a is not in the picture anywhere here we have not generated any instruction to move the result into a not yet that is why this updating the register descriptor for a becomes very important assume that l is a register most of the time it is. So, let us assume that l is a register now some register r contains the value at execution time after this instruction is executed. So, that is the value of a. So, we are going to associate r with a in both the descriptors in the register descriptor we will say r and a are associated and in the address descriptor we will say a and r are associated. So, now remember a is home location is not updated it is now just present in a register. So, we must keep this in mind when we want to empty the register we will have to move the generate an instruction to move the value of the register into the home location for a. The second updating that we need to do or the book keeping that we need to do is if b r c b and r c have no next uses. So, next use is available in the symbol table. So, if there is no next use that means we must update the descriptors to reflect this information. So that there is no need to you know keep the values of b and c in the registers that they are supposed to correspond to. So, we can discard that value. So, what is left is to discuss the function get reg. So, it finds the location l for computing a equal to b of c first of possibility. If b is in a register say r and r holds no other names this is important. If r corresponds to many variables then we cannot return the register r because we would be destroying more than one variable. So, we do not do that. Secondly b has no next use and b is not live after the basic block. So that means the value of b will be used in this instruction and then it is useless. So, in such a case if b is available in a register we can very happily use that register to store the final result also this is the best possible situation. So, return r suppose this is not possible failing one return an empty register if available. So, we will have a stack of rather a set of registers available. So, return one of the empty registers, but if we have used up all the registers then two is also not possible. So, failing two if a has a next use in the block that means a will be used again or if b of c needs a register. So, you cannot do without this operation of cannot be done without a register for example, op is an indexing operator. So, we have a equal to b square bracket c. So, in such a case the op is an indexing operator which can be executed only using a register. So, in both cases we must compulsorily assign a register to l and there are no registers which are free. So, we must use a heuristic to find an occupied register and then empty it. So, what possibility is exist in empty you know in finding an occupied register. One possibility is a register whose contents are referenced farthest in the future. So, the basic block has many instructions and at a particular point you know the register that we want to free may be used only much much later. So, we can say may be there will be a free register at that time. So, let us not worry too much and free that register. So, this is a heuristic to find an occupied register. Another possible heuristic is the number of next uses is the smallest. So, look at all the registers see which one has the smallest number of next uses smallest number of next uses. So, then you know we can release that particular register which has the smallest number of next uses. So, that means if we release the register which has the smallest number of next uses the number of loads for that particular variable will be kind of minimum. So, these are the two popular heuristics which are used in local register location. So, we have found a register now, but it contains some valid value. So, we must spill it by generating an instruction move r comma mem where mem is the home location for the variable in r. So, and that variable obviously should not be already available in mem if it is then there is no need to generate this instruction. Then obviously, we must update the register and address descriptors to say that this value which was corresponding to this r is you know now will hold a different variable and so on and so forth. If a is not used in the block or no suitable register can be found then return a memory location for l. So, this is possible provided the architecture permits you know memories law operands also as instructions. In the case of risk architectures you cannot have any other instruction except load in store with memory operands. So, we must compulsorily use a register when we use when we whatever operation is to be performed. If there is not the case then we can return a memory location. Here is a simple example of how to generate code for a small basic block. The variables t u and v are temporaries they are not live at the end of the basic block. W is a programmer defined variable or non-temporary and it is live at the end of the basic blocks and we have two registers available to us. So, at the end of the basic block we must store w back into its home location. So, that and we can ignore the values of t u and v which are present in registers at the end of the basic block. The first quadruple is t equal to a star b the basic block you know we are just at the beginning of the basic block. So, of both the registers are free they do not contain any value. So, obviously the only possibility is to generate a return the register of R 0 that is an empty register because neither a nor b are in registers. So, because we return R 0 and it does not contain a we generate instruction load a comma R 0 then mult b comma R 0 assuming that you know memory instructions are possible. Now, the register and address descriptors apparently read R 0 contains t and t in R 0 right. So, t is in R 0 and R 0 contains t the next instruction is u equal to a plus c now we destroyed a right. So, here a was loaded into register R 0, but then we destroyed R 0 sorry not a now we need to load it again from the memory location corresponding to a to the register R 1 because R 0 which contain t has a next use it will be used later. So, we do not want to release R 0 and there is an empty register. So, we will release R 1 a was not present in any register neither was c. So, we released R 1 since a was not present in R 1 we moved a into R 1 by this instruction this was generated and then add c comma R 1 that is the operation. Now, the descriptors read as visual R 0 contains t and t in R 0 from the previous instruction and then now R 1 contains u and u in R 1. So, now we have run out of registers no more let us see what happens v equal to t minus u fortunately t is in a register right and t is not live at the end of the basic block it is a temporary. So, we can use the register of t as the register for the result. So, that is R 0. So, sub R 1 comma R 0 puts the value of v into the register R 0. So, this is the instruction which is generated. Now, the descriptors are updated to reflect this R 0 contains v u in R 1 v in R 0 R 1 contains u. So, this is what it reads the last instruction in the basic block is w equal to v star u. So, v is in a register that is R 0 u is in the other register that is R 1. So, that information is obtained from the descriptors automatically. So, this address descriptor tells us that it. So, happens that both v and u can be dispensed at the end of the basic block. So, we do not need those registers they are not live at all. So, we can the register of v can be used to store the value of w. So, mult R 1 comma R 0 is generated. Now, R 0 will contain the result and it is assigned the it is actually going to contain the value of w. So, the descriptors now reflect that as well. At the end of the basic block since w is live we must generate an instruction to store R 0 into its home location w. So, that is restored. So, that ends the basic block code generation. So, this is how we generate code. So, let us see better schemes of code generation now. The basic block code generation that we discussed does not guarantee any optimality. So, it simply says whenever something is available in a register let us try to keep it in the same register as long as possible. So, there is some reuse, but the literature also explains you know carries descriptions of what are known as optimal algorithms. So, we are going to discuss two of these one is the Sethe-Ulman algorithm which is somewhat restricted and other is the dynamic programming based algorithm which is more general. So, let us begin our discussion on the Sethe-Ulman algorithm. The Sethe-Ulman algorithm generates the shortest sequence of instructions for a particular machine model and it possibly provably optimal. So, it is with respect to the length of the sequence. So, the optimality is with respect to the number of instructions that the algorithm generates for a particular program. Again, this is at the basic block level and the basic block is assumed to be an expression tree. So, if it is a DAG it does not work. So, we need to break the DAG into trees and then apply the Sethe-Ulman algorithm on each tree separately. So, what is the machine model that is used here? The machine model says all the computations are carried out and registers. So, in other words computation itself cannot use any registers, but there are some exceptions. So, instructions are always of the form op r comma r op m comma r. So, this is the these are the only two possibilities as far as the instructions are concerned. So, but the major or most of the major computations are all carried out in registers. So, this op can only be a load or store. So, we cannot really have a plus or minus or star as this particular op it has to be this op. So, we will have to do that. It always computes the left sub tree into a register and then reuses it immediately. So, this is very important the left sub tree must be computed into a register and then reused immediately. This is required even for the proof of correctness and optimality etcetera etcetera. So, we will see how to you know liberal make the instruction formats a little more liberal later, but in this case they must be of the form op r comma r op m comma r. There are two phases in this particular algorithm. The first phase is called the labeling phase and the second phase is called as the code generation phase. What is the labeling phase? The labeling phase is very important it tries to it computes the minimum number of registers required to evaluate the tree with no intermediate stores to memory. So, let me show you a picture. So, if you take this particular tree expression tree right the labeling algorithm computes a number called I know which with value 2 as the min-reg value of this particular tree. The implication is you require 2 and definitely not more or not less than 2 registers to evaluate this tree and how there will be no stores into memory locations at any one of the descendants of this particular tree every result will be stored in a register itself. So, this is the significance of this particular labeling algorithm. So, what does it say? It says if n is the left most child of its parent then label of that node is 1 otherwise label is 0 and for internal nodes it takes if i 1 is not equal to l 1 is not equal to l 2 then label of n is the max of l 1 comma l 2 and if they are equal it simply increments it by 1. So, l 1 plus 1 if l 1 equal to l 2. Let me explain the algorithm with this example. So, we have this tree. So, for this parent this is the left most leaf. So, this becomes a 1 this becomes a 0 similarly this is the left most child. So, this becomes a 1 this becomes a 0 again this becomes a 1 and this becomes a 0. So, these are the leaves for this parent this l 1 is not equal to l 2. So, the max is 1 similarly for this it is 1 and for this also it is 1 for this parent l 1 equal to l 2. So, this is l 1 plus 1. So, this is 2 and for this parent again l 1 not equal to l 2. So, this is max. So, this is 2. So, 2 is the Mindrack value. So, let us see how the code really evaluates this tree. So, this is very simple I can load this value into a register. So, it value goes into a goes into r 0 that is the instruction and then this can be in memory. So, I can say op of this r 0 comma b then the result will be in r 0. Similarly, this result will be computed into r 1. Now, I have result in r 0 and r 1 and the final result of n 3 will be in r 0 I can compute this in r 0. Now, the register r 1 is free. So, I take that here I will I compute e into r 1 then n 4 into r 1 by op r 1 comma f and then finally, r 1 and r 0 contain these two operands. So, this result can be computed into r 0 by op r 0 comma r 1. So, this is the way I can compute the tree into a register without intermediate stores into memory locations with just two registers. We will stop here and continue the lecture next time. Thank you.