 Welcome to part 2 of the lecture on object oriented languages. Today, we will continue our discussion on variable name visibility, mapping method names to methods and so on and so forth. To do a bit of recap this is the diagram with class hierarchy that I showed you last time. So, here there are 3 classes 1, 2 and 3 the class number 3 is at the highest level in the hierarchy 2 derives from 3 and 1 derives from 2. So, this is the way the hierarchy is maintained. Now, there are methods that 3 defines for example, it defines the method phi and it also defines the method form. Phi is not shared between 3, 2 and 1, 2 has its own procedure method called phi and similarly 1 also has its own method called phi. So, that part of it is not shared whereas, the method form which is available in class 3 is shared between 2 and 1 both. So, the class 2 does not define its own form of form similarly, class 1 also does not define its own form of form and similarly, class 4 is not a member of class 3 sorry method 4 is not a member of class 3, but it is a member of class 2 and it is shared between 2 and 1 it is provided by class 2 and it is used by class 1. So, this is the difference as far as the methods are concerned. So, similarly we also have class 1 which defines a phi actually phi which is its own function and it is not actually available to any other class as well. Now, there are objects a, b and c. So, the object c is of type 2 and it has 2 fields x and y, object a and object b are both of type 1, they have 3 objects 3 fields x y and z. So, the most important thing here is to find out which name maps to which method when a method call is made and similarly, whenever we use a field name can a particular method actually access that field at all. So, the point here is if we actually take the you know object of type 1 that is object a and we try operating with the method 4 which is actually defined in class 2 then the field z of this object will not be available to this method 4 because any object of type class 2 will not have any object you know field z inside it it will have only x and y. So, x and y will be visible to the method 4, but z will not be visible to the method 4, but the z will be visible to actually the method phi you know this here we have method phi and here we have method phi. So, both of them will be able to access this particular field z of you know object a. So, there are visibility rules of this kind and then the naming conventions as well. So, mapping names to methods implies finding out which particular method that the name corresponds to. So, if we say we are calling a dot phi then does it correspond to you know 1 dot phi or 2 dot phi or 3 dot phi. Obviously, when it is object of type you know 3 this thing 1 that a belongs to then it would be 1 dot phi if the call was made from an object which corresponds to class 2 then it would be 2 dot phi. And similarly, if it is made from an object of type class 3 then it would be you know 3 dot phi. And then what about a name such as a dot 4 a is the object and we are trying to make a call 4. So, 4 is defined if you remember 4 is defined only in class 2 and it is shared between 1 and 2. So, 4 invokes 2 dot 4 always. So, if and a dot form similarly invokes 3 dot form because 3 is the only class which defines this method form. So, this is the difference. So, conceptually when we want to determine the method which corresponds to a particular name the method look up actually is nothing but a search for the name of that particular procedure call. So, these are actually called virtual calls and search for that method in the objects class that is the receivers class from which it was made you know the first of all we check whether it is a local method available in the local class itself. And if that fails we go to the parent of this particular class find out it if the method belongs to that super class and so on and so forth. So, this this method may become very efficient provided the complete method table is stored in each one of the classes. So, I will you know show you what I mean very soon. So, if the complete method is a table is not stored in each class then the the search may take a little extra time. So, that is the difference between you know the search strategies. Now, the next one is variable name visibility. So, suppose we call the method b dot phi. So, phi is defined in each one of the classes. So, there is a local phi which is available in the classes 1 2 and 3 all of them. So, it allows phi to access all the variables instance variables of v. So, there is no problem because it is a local you know method that is defined inside the receivers class. So, there are three variables x y and z that I already showed you this diagram. So, there are three variables x y and z corresponding to every variable every instance variable of class 1 right. So, the class variables you know of 1 2 and 3 they actually have different types of variables. So, class 1 of course has only x and y 2 and 3 both have x y and z. So, depending on which method is actually invoked the whether the particular variable is visible or not will be known. So, in the same breadth if we call b dot 4 instead of b dot phi then b dot 4 is actually you know. So, let us look at this. So, b is here. So, b is defined as that of class 2. So, this is our a and b right sorry class 1. So, b belongs to class 1. So, that actually has three variables in its three objects three methods sorry three of the fields x y and z and if whereas, the method 4 belongs to class 2. So, anything from class 2 will be able to see only x and y it cannot see z because every object of class 2 has only two fields x and y. So, z is invisible to method 4 whereas, it is definitely available to the two methods phi and phi. So, this is the two when phi is called from an object of type class 1 not from an object of type class 2. If the method phi is called from an object of class 2 then you know it can again see only x and y. So, the moral of the story is whenever there is a method call you have to see which class that method really rather the object that the we are using right that point belongs to and then determine the instance variables which are relevant to that particular class and then say these are the ones which are visible at that point. So, 4 can also access class variables of classes 2 and 3, but not the class variables of class 1. So, this is the difference. So, 4 which is declared by class 1 you know rather class 2 cannot access anything, but the variables which are defined in the in its own class. So, this is something that one has to remember very carefully. Now, that is about visibility. So, what is visible to each method right and now how do we generate machine code for the methods. So, methods can access any data member of any object that becomes its receiver what is a receiver every object that can find the method. So, of course, everything is subject to class hierarchy restrictions which I already mentioned. Now, there is a basic requirement from the compiler. The compiler must be able to establish an offset for each data member that applies uniformly to every receiver. This sentence will be very clear very soon. The basic difficulty is there are several methods that can use the you know fields within an object. The methods could have been in the parents class or in the current class. So, the offsets that apply to the parents class or the current class must all be the same that is what this really says. The compiler can construct these offsets as it processes declarations for a class. Objects contain only data they do not contain any code. So, let us consider the cases one by one the first one is no inheritance. So, in other words there is a single class there is no inheritance and here is a very simple example class giant. So, it has phi, phi, phi and form. So, all the methods are actually not necessarily defined within this class, but here we are assuming that there is no inheritance. So, all these methods are actually defined within this class. So, they are not inherited from any other class. Then there is a static variable called n and then two integer instance variables x and y. So, the record for the class. So, this is called as the class record name of the class is giant. So, the giant class record it will have a field for the static variable and then it also has pointers to the code for each one of the methods that it has. So, form for phi and phi and phi of course, this nu is a is something that is available everywhere. So, because we need to create new objects of that particular class that is provided by the system. So, the object layout will have two fields x and y. So, here also you can see two fields x and y and there is a field which points to the class record. So, that is one of the other fields as well. So, it is very simple whenever there is a single class and there is an object let us say joe. So, if we say joe dot foe automatically the compiler can you know rather at run time we can the compiler would have generated code for the you know offset of this giant dot foe to be accessed and then the call to giant dot foe to be made appropriately. The offset is actually static it has been determined by the compiler because this is the method table that has constructed by the compiler. So, there is no resolution left you know for the run time at all. So, nothing is dynamically done it is all statically done the compiler simply has to take the address of the class record the beginning of the class record from the object. Then it knows the offset of the method foe and it adds that to the beginning of the class record then you know that pointer gives you the beginning of the code for the method foe and a call is made to that particular method. So, it is a fairly straight forward process as far as you know single class and no inheritance is concerned. What happens if we have inheritance the situation is slightly more complicated. So, again we have a similar diagram there is a super class called SC there is another derived class called MC and there is a third class called giant which is derived from MC. So, here is you know a super super class record to which all classes actually have a pointer. So, this contains some extra data and you know about methods and other variables etcetera which are required for maintaining the entire system. So, let us not worry too much about this part of it these are language specific details we need to worry about the method tables here. So, here we have a the method new is available in all the classes. So, there is a pointer to the code for method then the method foe is defined by SC. So, there is a pointer to foe the method from is also defined by SC. So, there is a pointer to the code for form there is a static variable and that is present in the class record itself. The super class pointer is set to null here for SC because there is no other super class above it whereas, for MC it points to SC. So, here also we have the complete you know method tables. So, MC defines of course, new is already provided by the system then we have fee and then we have foe all right. So, these are the only three which are provided by this particular class whereas, the form is provided by SC. Similarly, giant provides new which already available by the provided by the system it provides fee and it also provides phi rest are provided by foe as such. So, then there are variables x y and z for each of the objects which are of type giant and if there is an object of type MC then it would have only two fields x and y. So, let us see how to generate code for this type of inheritance hierarchy. The basic idea is very simple there is a class pointer class record pointer in each one of the objects then there is some space for the objects of the you know highest super class that is SC because the if you consider an object of the class which is at the lowest level of the hierarchy that is giant in our case then giant the any data object of type giant must accommodate data members of giant which are very specific to that particular class it derives from MC. So, it must provide for space for the data members of MC and MC in turn derives from SC. So, giant actually transitively derives from SC. So, the data members of SC must also be provided for by the object layout. So, there are several components in the object layout whenever there is inheritance they are for each one of the super classes in the hierarchy the data area some data area has to be set apart in the object layout of the object. So, every instance variable has the same offset in every class where it exists up in its super class. So, this becomes clear in the picture that I am going to show you now. So, let us not consider the method table for the present let us look at only the yellow part which is the data record. So, there is a class pointer then we have SC data these are the giant you know object data layouts. So, then there is SC data MC data and giant data. So, observe that SC data and MC data is also present in objects of type MC and then only SC data is present in objects of type SC. But the most important thing is the space that is provided for the SC data in the you know any object record of type class SC must match exactly with the SC data space that is provided in both objects of type MC and objects of type giant. So, that is a very important thing. So, if there are five variables let us say here there is only one if there are five variables in SC and they are listed in a particular order in the SC class record then rather SC object record then they must be listed exactly in the same order in the derived class objects as well that is MC objects and giant objects. The same is true for MC data when we look at object layouts of you know objects corresponding to class MC and class giant. This of course, will not have any information about MC because this is the super class and MC is the sub class. So, the space provided for MC data here and the MC data here must match the amount must match and the offsets must also match. So, the offset of the MC data area is this much this is the offset it must be the same offset here also and that is why the giant data members actually come at the end. If the giant data members were to be here then you know it would be really chaos whenever we call a method corresponding to class SC from an object of class or giant it would actually treat the first area as SC data. So, if we had giant class data here it would be incorrect data as far as that method is concerned. So, when a class you know the method tables also follow a similar sequence as above. So, and then there is another very important point before we move on to that picture when a class redefines a method defined in one of its super classes the method pointer for that method implementation must be stored at the same offset as the previous implementation of that method in the super class I am talking about complete method tables. So, let us understand what this really means. So, here is the method pointer table for the you know inheritance hierarchy here. So, consider the class record for class SC that is the one which stores the method tables. So, there is a class pointer of course, then the super class pointer there is a new pointer this would be common for all of these and then the pointer to fee and pointer to form. Now, what we really say fee is of course, redefined by the class MC and it is also redefined by the class giant, but the pointer to fee is now let us say the first second third fourth right in this class record it must be fourth in the class record for MC and it must be the fourth one in the class record for giant as well. So, it is so if it is not then you know it is not possible to statically say this is the correct place at which the pointer to the method fee exist. So, there must be some uniformity in these class records to store the appropriate method pointers. Then we have a fum pointer here fum pointer actually is implemented by the class SC and it is shared by classes MC and giant as well. So, the fum pointer is in the next one and it is actually in the same position as far as class record SC MC and giant are concerned. So, that is those are the only ones which are provided in class record SC. Now, between MC and giant again we have the pointer 4. So, pointer 4 must be in the same position as far as relative position as far as these two class records are concerned and then the five pointer of course, is at the end. So, in a nutshell all the information which is shared with the classes above should be prior to all the information that is provided specially in a particular class. So, phi for example, is a special method provided in the class giant it is not present in the super classes. So, that should come actually at the end. Suppose we do not store the entire method table right. So, in that case it really does not matter. So, we store only the corresponding methods which are changed or which are new, but whenever we provide for a common method across. So, we redefine phi in both MC and giant. So, it must be present in the at the same offset as far as all the three class records are concerned. But then fum is provided only in this, four only in this and phi only in this the rest are all shared. So, in this type of you know non shared rather incomplete method tables the class hierarchy search must be done from here to here. So, whenever there is a method name say for example, fum it is searched in this table it is not found then it goes to the super class searches in that table it is not found and then it goes to this it searches in this table it is found and then it is accessed and executed. So, if we store only the changed and extra methods then the class method tables become very compact, but unfortunately the space the time required to access the method increases. Whereas, if we actually store if we store the complete method tables then the space required to store the method tables increases, but the access to methods becomes much faster. So, this is the difference between the you know storing the complete method tables and storing only the changed or extra and extra method tables. The next important thing that we need to do is to understand how exactly type inclusion tests are out. So, first of all let us understand why type inclusion test is required at all. So, the need so here is a very simple example class y is a sub class of class x. So, let us assume that now there is a declaration x a equal to nu y. So, a is of type base class of y. So, the y is the sub class and x is the super class. So, x is above y and now we are actually declaring x a which is a an object of type super class and we point it to the sub class object nu y. So, that is the that is what this declaration says. Then there is a statement y b equal to a. So, b is an object of type y that is the sub class and now it points to a which is again of the same sub class y. So, that is not an issue both are correct. Now, because a even though it is a base class pointer it points to a derived class object which is permitted by the language and here b is a sub class object derived class object and now it is being assigned a derived class object again which is correct. So, this is correct, but the code below here this is wrong. So, instead of x a equal to nu y we said x a equal to nu x. So, now a points to a super class object now we say y b equal to a. So, whereas b is a sub class object. So, we really cannot make it point to a super class object it is always possible to make a sub super class pointer point to a sub class object, but making a sub class pointer point to a super class object is not permitted. But how does the compiler or the runtime system determine that these objects are of correct types the pointers are of correct types. This requires runtime type checking it is not possible to determine you know the types of b and a in general at compile time in all cases it is necessary to determine these types only at runtime. So, and secondly java has an explicit instance of test that requires runtime type checking. So, I can say instance of and given object and then it should return the class to which it belongs to. So, that is also something that is very important in the java language and its programs. So, if in all for all these reasons we will require methods to do type inclusion test at runtime. How does one do this simplest would be to store the class hierarchy itself. So, if you store the class hierarchy graph in memory. So, for example, this is a class hierarchy graph. So, this is the highest super class and all these are sub classes. So, this means single inheritance whereas, this is really multiple inheritance. So, if there is a single inheritance then you know I have a single parent. So, d has a parent b b has a parent a whereas, if there is multiple inheritance for example, consider e or c right e has two parents b and c. Similarly, c has two parents a and h and so on and so forth. So, multiple inheritance is always very difficult to manage. So, and we are not going to study this topic in our course this is meant for advanced study. So, if you have single inheritance how does type inclusion work. Suppose, I am given an object right then this is a class point and this node in the class hierarchy graph can be obtained through the class pointer that is not an issue at all. So, once I get the class pointer I can check its parent and its parent and so on and so forth. So, now class the type inclusion test can be performed if the question is e is d a sub class of a. We start from d go all the way to the root and in the path we get a then the answer is yes otherwise the answer is no. For example, for d and a we get the answer yes, but for d and e we do not get the answer as yes. So, because we reach a which is the highest super class, but we did not meet e on the way. So, that is why search and check if one node is an ancestor of the other and the traversal is quite straight forward to implement only for single inheritance. It is very cumbersome and slow for multiple inheritance because we need to check all paths execution time increases with depth of class hierarchy. So, for example, if we want to determine the type inclusion for b and c is b an instance of a is c an instance of a etcetera or is b an instance of c whatever the question is. If you want to do the inclusion test for nodes at this level then you know one hop is enough to determine whether the answer is yes or no. Whereas, for nodes at this level it requires two hops and for the node at this level it requires three hops to determine the answer. So, if you have deep hierarchy then the amount of time needed to determine type inclusion would be more. So, for the nodes which are at the lower level. Another way of storing information about the class hierarchy is to use a binary matrix. So, the matrix has columns which are named as the after the classes again it has rows which are again named after the classes. So, class types on this side and class types on this side as well and if we have say class c 3 and we want to check whether it is a descendant of class c 4 we just have to look up the entry b m of c 3 comma c 4. So, if the entry says 1 then yes indeed the answer is yes and if the entry is 0 then the answer is no. So, b m of c i comma c j equal to 1 if and only if c i is a sub class of c j. So, even the transitivity is taken care of here. So, it is not just one level, but all the levels of the hierarchy are coded as 1 or 0 inside this binary matrix. Tests are very efficient, but the matrix will be very large in practice right. So, we have this matrix which is quite large and the space required for the matrix once it is very large. Thus access method you know actually has to be cut down will be quite low, but to store very large matrices we actually use some compaction. So, if the matrix is compacted then you know b m accessing b m of c i comma c j is not going to be an over time access, but it will be much more than that. So, if the matrix is available in the form that is shown here then accessing it is very easy, but if it is coded then you know access time also increases of course, this major advantage of this matrix approach is that it can handle multiple inheritance as well. Whereas, searching the class hierarchy using the tree or the graph was much more difficult. There is also another very elegant method which works only for single inheritance and extensions to handle multiple inheritance are very difficult very complicated. This is called relative or Schubert's numbering. So, basically we are given the class hierarchy that is at compile time. So, we do not have to store the class hierarchy at run time if we have to then the method has no advantage. So, let us assume that there is single inheritance and the class hierarchy tree is given to us. So, here is the class hierarchy tree. Now, we do a post order numbering of the nodes of the class hierarchy tree. So, the second component in each one of these pairs is the post order numbering. So, obviously, post order walk over this tree goes to starts with d. So, this gets 1 then it goes to the parent it gets 2 then it goes to the parent which has a right child. So, we go down the right subtree all the way to g that gets a number 3 then the this e gets number 4 and then we go to c and go down the right subtree that gets number 5 then c gets number 6 and a gets number 7. So, this is the first step as far as you know this relative numbering goes. Now, we need to determine the first component. So, second component is already determined this is the r d m l number of the node a in a post order traversal of the tree. So, what is the first component l a. So, to define that we must define this relation called sub type of. So, let this angle you know this bracket with an underscore denote the sub type of relation. So, all descendants of a node are sub types of that node. So, obviously, that is true for a inheritance tree anyway. So, this sub type of relation obviously, is reflexive and transitive that is very easy to see you know. So, we have d is a sub type of b and b is a sub type of a. So, d is obviously, a sub type of a and every node is a sub type of itself. So, reflexive and transitive properties are true. What is l a? l a is minimum of r p such that p is a descendant of a. So, if I consider a node such as a then we look at the post order numberings of each of the nodes find out the minimum. So, in this case obviously, one is the minimum. So, one happens to be l a of this particular node. The same is true for b it is among its descendants one is the minimum. So, one is a is the you know the first component of this as well. Obviously, the one is first component first the component of d as well. When we come to see the minimum happens to be 3 again. So, 3 is the first component of this node this node and this node and for this node this is on its own. So, 5 is the only thing that appears. So, 5 is the first component of this node. Now, the test says a is a sub type of b if and only if r a is between l b and r b. So, if I take the post order numbering of the node a it must be located between l b and r b of node b that is what it says. So, let us check it out let us check it out for d and b. So, the post order numbering of d is 1 this is between 1 and 2 perfectly correct. So, d is a sub type of b and obviously, d is a sub type of a because 1 you know is between 1 and 7. Now, let us consider b and e. So, e is a sub type of b the sub the you know post order numbering of e is 4 it is obviously, not between 1 and 2. So, e is definitely not a sub type of b and e is b a sub type of e that is also not true because the post order numbering of b is 2 and it is definitely not between 3 and 4. So, that is also false. So, this is a very simple check once we have at compile time once we have the inheritance tree we do the post order walk compute the second component then you know we compute the minimum and compute the first component. So, now the entire tree is decorated we can store the numbers these Schubert numbers or relative numbers of each one of the nodes along with their class pointers. So, that is very easy. So, given the class pointer you know we we access the relative number and then determine whether this relation l you know rather this inequality holds l b less than equal to r a less than equal to r b. So, this is a an order 1 computation very fast, but is and of course, remember that this is if and only if. So, if this holds then a sub type holds and if this is sub type then this inequality holds this holds only for works only for single inheritance, but that is what we are really studying today. So, that is you know that is about type inclusion. Let us look at one optimization which is performed on object oriented languages which is a fairly straight forward, but very important optimization. This is called d virtualization and it can be performed using what is known as class hierarchy analysis. Basically it reduces the overhead of virtual method invocation and statically determines which virtual method calls can result to a single method. So, search calls are either in lined or replaced by static calls. So, basically we want to avoid searching the method tables at run time, but this can be done only in certain number of cases not all cases. Whenever we can do it we we know the target we replace the method search, method table search you know which can be avoided and we replace that by a static call an ordinary procedure call. Of course, this requires a class hierarchy and a call graph let me show you an example of how to do that. Here is a small program here is class x that extends the object in java. So, here is object then x is derived from it it has two methods f 1 and f 2. Then y is derived from x and it has a it redefines the method f 1. Then again z you know inherits from x and it redefines the method f 1 as well. Now, within the class z there is a main program. So, that is this z dot main z dot main has three calls a dot f 1 b dot f 1 and b dot f 2. So, here for example, x a equal to nu x y b equal to nu y and z c equal to nu z. So, if something then a equal to c right. So, a is actually an object of type class x and c is actually a an object of type class z now we say a equal to c. So, now a can point to either a type z node or z z object or it can point to you know a type x object either one of them. So, that is the difference. So, if we do not know to which one of these it points to we may not be able to determine statically the method that is actually executed from a and that is precisely what happens here. So, we call a dot f 1. So, when we do this if it was pointing to an object of type x then it would be x dot f 1, but if that was pointing to an object of type z then it would have been z dot f 1. So, the call graph points to you know a dot f 1 points to both x dot f 1 and z dot f 1 and there is no way you can actually resolve this call at compile time, but if you take b dot f 1 it always points to only y dot f 1 rather it call the call is always y dot f 1 not points to. So, there is no change in what b points to similarly b dot f 2 is always x dot f 2, because b does you know this class y and class z do not define their own f 2 it is always in x. So, b dot f 1 can be you know the there is no need to search any method tables here it can be directly coded as y dot f 1 and b dot f 2 can also be coded directly as x dot f 2. So, these become static calls whereas, a dot f 1 cannot be resolved at compile time one has to generate code to check the type of a. If the type of a is x then the call materializes as x dot f 1 and if the type of a is z then the call would be translated to z dot f 1. So, this runtime resolution will be definitely necessary in certain cases not in all cases and whenever we can resolve the call to a static call we can do that. So, that is about our I mean object oriented language implementation now let us move on to a new topic global register allocation. So, in global register allocation we must understand why we require global register allocation what are the various issues define the problem properly. And then we study three methods of doing global register allocation and it is in the increasing order of complexity first one is based on what are known as usage counts. The second one is known as a linear scan register allocator and third one is based on graph coloring. Let us understand the issues in register allocation the question to be asked is which values in a program reside in registers. So, now we are looking at register location as such there may be 10 variables 10 values available I know which are used in the program when the program runs which of these values must reside in registers otherwise obviously, it would reside in memory. So, to make an intelligent decision about this the of course, the best answer would be take those variables which are accessed you know very frequently and maximum number of times and then make sure that the you know the run time of the program the execution time of the program is minimized when we place these values in registers that is the correct way of doing it. But can we actually do all this assessment at compile time and make the allocation so that the execution time is minimized not necessarily the reason is allocation of registers is a very complicated task we can only do it in an approximate way the problem in general is NP complete. Then the second question to be answered is in which register so this is called as the register assignment problem. So, usually the two are loosely referred to as register location because in modern architectures the except for one or two registers all other registers can be used interchangeably. If there are very special purpose registers then this problem becomes more difficult what is the unit at which you know register location is done. For example, if it is a local register locator then it does register location at the level of basic blocks whereas, if it is a global register locator then it does it at the level of a function or it does it at the level of a group of functions called regions. Or of course, it could also be a group of basic blocks in a program and that would be a loop or a nested loop or something like that register location within basic blocks is called local register location and other two are called as global register locations. Now, global register location obviously requires much more time than local register location and all register locations are actually carried out at compile time of course, there are you know exceptions to this we will see that very soon. Now, phase ordering between register location and instruction scheduling is a well known problem instruction scheduling is nothing but reordering of instructions. So, that a pipeline hazards are eliminated. So, if we do register location first then the movement of code during instruction scheduling is restricted. So, this is not you know recommended whereas, if we you know schedule instructions first then during register location we may have to introduce extra code called spill code and that would not be scheduled at all. So, what we do as a practical in practical situations is do scheduling first then do register location and then require do scheduling again. So, to handle this spill code. So, two passes of scheduling would be done. Now, there is a trade off between speed and quality of allocation as well even at whether it is compile time or whether it is run time. So, let us see what happens. So, in some cases for example, in the case of just in time compilation of java compile time is actually you know limited to producing the java byte code, but during the execution of the java byte code there is a phase of machine code generation which is called just in time compilation which is performed to increase the speed of that code. So, in such a case if we perform a very complicated register location task within the just in time compiler it would really require too much time you know. So, performing a very complicated register location in just in time compilation you cannot be justified in such cases that is the first issue. Second issue is should we perform both local and global location yes that is possible we could actually do global allocation first and then perform local allocation. Now, let us define the problem of register location and see what it really means. So, global register allocation assumes that allocation is done beyond basic blocks and usually at the function level and the problem is you are given an intermediate language program. It is represented as a control flow graph you are given you know k registers as well. So, that is the number of registers available is there an assignment of registers to program variables such that no conflicting variables are assigned the same register. In other words two variables which actually are live at the same time should not be given the same register. Of course, there must be no extra loads or stores introduced because of this assignment and at most k registers are used you could use less than k registers, but you cannot definitely use more than k registers. So, this problem so that is again let me summarize you are given k registers there are many variables in the program. So, what the can be is there an assignment of program variables such that no conflicting variables are assigned the same register and we do not use more than k registers at that time any point in time. So, this problem has been shown to be NP hard by Ravi Seti and we use a very popular heuristic called graph coloring to solve this problem, but there are other many other simpler algorithms as well. So, the difficulty here is if there are a very large number of registers then you know the it is possible to assign variables to registers very easily. The problem starts when there are very few registers and we have to intelligently assign variables to the registers. So, some of the registers some of the variables cannot be assigned registers. So, they will have to remain in memory. So, that is the basic problem here graph coloring is used as a popular heuristic to perform this allocation. So, in graph coloring to very briefly the colors are the registers we construct what is known as a conflict graph and then apply reduction on the conflict graph to achieve coloring. So, we will stop at this point and continue with the discussion on graph coloring in the next lecture. Thank you.