 Welcome to this new course on principles of compiler design. So, in this lecture I will give you an overview of a compiler, but before that we will also see how exactly the course is organized and then the motivation for studying compiler design and of course then go on to the details of a compiler with block diagrams. So, the course is actually a first level course. In other words this takes a detail look at the internals of a compiler and I do not assume any background for this particular course, but it is a very intensive course. So, the pace of the course is going to be you know not very slow, but at the same time it is not going to be racy either, but the students who are actually going to take this course seriously are requested to do the programming assignments. They are also supposed to solve theoretical problems which I am going to suggest otherwise this course will not be understood you know properly. The reason is a compiler is an excellent example of the theory being translated into practice and this is a wonderful example of that. So, let us see the motivation for studying a compiler. So, compilers are really everywhere. So, if you look at the applications of modern compiler technology pick up the browser open it and then immediately you know there would be html files which are displayed on the home page and that you are going to visit and so on. So, the html parsers are based on compiler technology and then behind the scene there is javascript, there is flash etcetera running inside the browser and the interpreters for this javascript and flash etcetera are also based on the modern compiler technology. Then of course, for the compiler itself we require machine code generation and for high level languages whenever we need code generation we need to use compiler technology anyway, but then apart from that it has uses in software engineering as well. For example, software testing and then program optimization then in the security domain malicious code detection design of new computer architectures. So, why are these important? For example, if you look at the development of a new processor nobody builds a processor you know right away even if the design is 100 percent accurate and all that the performance etcetera will all be known only after the hardware is built. Therefore, there is a simulator which is built for a new CPU and then people also build a compiler for that particular CPU. So, once a compiler is built you can compile right programs in C or C plus plus or any other language compile those programs and then run them on the simulator. See what kind of performance it has giving if necessary make changes in the hardware. So, that is called the compiler in the loop hardware development and it is very useful perhaps very widely used by chip designers in various companies. Then again in the area of hardware synthesis nobody really writes you know the low level assembly type of code for generating VLSI designs and so on and so forth that is called RTL register transfer logic. People really write it in very high description you know high level description languages called VHDL or VDL and so on and then the compilers really generate RTL from VHDL again compiler technology is involved here and then the novel application of compiler technology is compiled simulation. Suppose, you write a program in VHDL how do you really find out the performance of the chip or whatever is designed using that VHDL. So, typically the a program a compiler is used to generate a simulator and the simulator is actually a computer program which is generated for that particular program which is being simulated. So, this is called simulation of the design and it is an example of compiled simulation there is no interpretation of the design here and hence such simulations are much faster than interpretations. So, about the complexity of compiler technology it is also necessary to say a few words about this aspect. If you look at the compiler it is possibly the most complex system software and writing it is a substantial exercise in software engineering. So, in fact in our institutes whenever somebody teaches courses on compiler design the associated assignments are really small compilers themselves and writing these is a fairly large software engineering exercise. The complexity of a compiler really arises from the fact that it is required to map a programmers requirements which are written in high level languages to architectural details. So, we are really talking about a C program and then it is translated into a machine level program. So, in between you know the compiler has to actually travel a long distance it is not as if it is a very simple operation. So, there is a huge amount of complexity here. So, we are going to discuss this particular complexity and the complex operation in its details in our course. A compiler uses you know algorithms and techniques from a very large number of areas in computer science we are going to see some examples very soon. So, it is not in that compiler technology is a subject on its own it has to borrow a huge number of techniques and algorithms from other areas in computer science. And compiler translates I already mentioned this intricate theory into practice. So, you take a for example, a context free grammar specification which is very formal, very precise and absolutely essential for writing a you know describing a language. It can be translated into a parser with minimal effort there are tools such as YAC which do this. So, this enables tool building. So, tools take very abstract specifications and generate programs from these specifications. So, now let us look at the type of algorithms which are used inside a compiler and see and let me show you how we require many types of algorithms here. So, we require results from mathematical logic lattice theory linear algebra probability etcetera. So, where are these used for example, mathematical logic is used to you know in developing type checking theory then lattice theory is used in developing static analysis dependence analysis is heavily based on linear algebra and loop parallelization is based on loop dependence analysis. So, cache analysis uses both static analysis and probability theory. So, in other words a very deep mathematical background is required to develop new compiler algorithms. In our course we are going to study the existing compiler algorithms, but we will also look at some of the you know the basis which actually make up this particular topic. Then there are practical applications of many algorithms for example, greedy algorithms are used in register location. So, heuristic search is used in list scheduling which is a part of a code generator, graph algorithms are used in dead code elimination register location etcetera. Dynamic programming is used in instruction selection that is how to generate machine code. Optimization techniques are used in instruction scheduling, finite automata play a part in lexical analysis, push down automata are very helpful for parsing, fixed point algorithms are used for data flow analysis, very complex data structures such as symbol tables, parse trees, data dependence graphs are going to be built. So, we use you know trees, balance the trees, graphs etcetera for such applications. Computer architecture itself uses machine code you know the is used in the knowledge of computer architecture is used in machine code generation. And then what are the other uses of some parts of compiler technology, some aspects of compiler technology. For example, scanning and parsing techniques are of course used in compilers, but they are also useful for assembler implementation. They are useful for online text searching for example, grep and arc which are available in unix are based on you know the scanning techniques. Word processing, website filtering, command language interpreters that is for unix for example, shell you know. So, is a command language come scripting language. So, interpreters for such languages are useful, scripting language interpretation again, Perl, Python, Unix, shell, XML parsing, documentary construction, database interpreters the list is very big. So, I have mentioned only a few of them which are very important. What about program analysis? So, one part of a compiler is scanning and you know performs scanning and parsing, and another part of a compiler performs program analysis. So, program analysis techniques are useful in converting sequential programs to parallel programs, and very important it can be used to determine if programs are data rays free. In other words, are there two parts of the program through threads actually accessing the same locations etcetera, etcetera can all be decided using program analysis. Profiling programs to determine busy regions of the code, well if this is done then we can possibly make that particular region of the code much more efficient. Program slicing techniques are used in debugging. So, a slice of a program is one small part of a program. So, data flow analysis approach to software testing is again based on program analysis. For example, uncovering errors along all parts, dereferencing null pointers, buffer overflows, memory leaks these are all common errors which actually occur in programs, and to some extent taking detecting such problems using software testing requires a data flow analysis approach. Then worst case execution time estimation and energy analysis. If you look at a program, it is very difficult to say what is the worst case time that it requires, because time of execution for a particular program is based on its input it depends on the input. So, finding out the worst case input is a very hard problem, and worst case execution time estimation is also equally hard, and uses program analysis techniques. Energy analysis implies the detection you know rather computation of the amount of energy that a program takes. This is as difficult or more difficult than time analysis. So, program analysis techniques are used in energy analysis as well. So, that is about you know the motivation to study this subject called compiler design. So, let us begin with this block diagram which talks about a general language processing system. So, to begin with we have for example, here a pre processor which takes a source program as input, and then outputs modified source program. So, even in the C compiler we have such pre processors. So, for example, you write hash defined macros or hash include you know directives inside program, then such macros are expanded by the pre processor, and the pre processor expands and provides the source program as input to the compiler. A compiler itself takes a clean you know expanded source program in a high level language such as C or C plus plus or any other language, and outputs this assembly program for the particular machine. So, we will see the details of a compiler in a short while. The target assembly program is input to the assembler which takes the mnemonics in the assembly language and translate them to actual binary machine code. Finally, the assembler output is called as the relocatable machine code which is combined with library files and relocatable you know object files of from other sources by the linker and loader to provide the target machine code which can run on a particular machine. So, now we zero in on the compiler itself. So, compiler consists of many blocks they are all listed here lexical analyzer is the first one and the output of that goes to a syntax analyzer. The output of which is again fed to a semantic analyzer following that is the intermediate code generator and then comes the machine independent code optimizer the machine code generator and finally, machine dependent code optimizer. And all these parts of a compiler use a data structure called as a symbol table which is actually somewhere in the middle. So, we now have for example, let us look at lexical analyzer and see what it does. A lexical analyzer takes as input a character stream. So, we are now going to take each of these blocks and study them in detail. So, let us begin with this lexical analyzer. But before we begin with lexical analyzer I must hasten to add that there is a difference between what are known as compilers and interpreters. So, if we look at the previous slide the compiler consists of this entire you know seven blocks along with the symbol table. Whereas, an interpreter stops after the intermediate code generation stage and the output of the intermediate code generator is fed to an interpreter. So, let us see the difference between these two. Compilers generate machine code whereas, interpreters you know interpret intermediate code. Of course, interpreters are only 50 percent of a compiler. So, they are easier to write and can provide better error messages because we are we still have access to the symbol table and so on. But the catch is interpreters are 5 times slower or more actually more than 5 times slower than machine code generated by compilers. So, running machine code is much faster, but interpreters are easier to build. So, people tend to write you know interpreters for certain types of languages whereas, they try to write compilers for languages which are used to write professional programs. Interpreters also require much more memory. So, and then you know even the compilation lexical analysis parsing etcetera is all the time required by the interpreter itself is added to the time required by the interpreter. So, they require much more memory much more time than machine code generated by compilers and there are very famous examples Perl, Python, Unix shell, Java, basic, Lisp etcetera are all you know interpreter based languages. Now, let us get back to the block diagram and let us look at the lexical analyzer in some detail. A lexical analyzer takes source program. For example, here there is a line Fahrenheit equal to centigrade star 1.8 plus 32. This is an assignment statement in any particular language you know there is no need to talk about a particular C or C plus plus such assignments are available in every language. So, now the lexical analyzer takes as input such sentences from a source program and then it generates what is known as a token stream. So, in this particular case the two names Fahrenheit and centigrade are all are both called identifiers. So, Fahrenheit is coded as identifier 1 and centigrade is coded as identifier 2. The equal to sign which corresponds to assignment is actually made an assign is made into a token of kind assign. Similarly, multiply operator is a mult op and then plus is made into an add op the constant 1.8 is a floating point constant i constant you know 32 corresponds to the number 32. So, in other words we now have a stream of these tokens. The first part of the token id assign id mult op f constant add op i constant these are typically integers they are numbers. Whereas, the second part whenever it is present is actually gives you hints about what type of you know the token it is for example, if it is id then that 1 and 2 may point to a table with indices 1 and 2 containing the string corresponding to the identifier the i constant f constant you know the second part will tell you the value of that particular number and so on and so forth. This is the input to syntax analyzer. So, now let us look at the reasons why lexical analysis is required very briefly. So, lexical analyzers can be generated automatically from regular expression specifications. So, for example, lex and flex are two tools available in unix and if we feed a regular expression specification we are going to study these specifications later in the course outcomes a program which works as a lexical analyzer. The lexical analyzers are actually is a deterministic finite state automaton each one of them is a finite state machine and we will learn what these are in the coming lectures, but now let us answer the question why is lexical analysis separate from parsing. It is not a theoretical limitation in other words the next phase of a compiler called parser can actually incorporate the lexical analysis also there is not much difficulty as far as theory is concerned, but practically if you look at the design a compiler is an extremely large piece of software millions of lines of code and simplifying the design making it modular is the only way its complexity can be controlled. So, simplification of the design by making lexical analyzer a separate module is a reason because of software engineering purpose. The second reason input output issues are very limited you know or very serious and it is not a good idea to distribute such input output you know issues all over a compiler they are best handled in one module and in this case in a compiler lexical analyzer handles all the input output issues. So, for example, it reads programs you know the from the source file and then any errors etcetera are all actually listed by the lexical analyzer after collecting them from various parts then and so on. So, lexical analyzers based on finite automata are more efficient to implement than push down automata which are used for parsing this is a very deep reason. So, as I already mentioned a lexical analyzer is nothing but a deterministic finite state automaton and a parser as we will see later corresponds to a push down automaton. A push down automaton uses a stack for its operation. So, if we actually try you I mean doing lexical analysis with a push down automaton then we will end up actually pushing a lot of symbols on to the stack then popping them off the stack and so on which are very inefficient operations and therefore, using incorporating lexical analyzer into a parser makes it very inefficient compared to the making a finite automaton based lexical analyzer. So, these are the reasons why lexical analyzers are separate and of course, if you look at the previous slide as I said each one of these tokens the first part of the token is an integer. So, storing these integers is much more efficient than storing the characters corresponding to the source program. So, it is actually a very you know succinct and compact way of you know giving the program to the syntax analyzer. So, then once we understand lexical analysis we must see how it actually helps syntax analysis the output of the lexical analyzer is fed to a syntax analyzer. So, we have these tokens coming into a syntax analyzer the syntax analyzer looks at the tokens and finds out whether the syntax is according to a grammar specification. In other words the assignment statement must have a variable on the left side an expression on the right side and so on. So, does this have you know the assignment operator is it correct or is it there is there a mistake and whether the plus star etcetera are all properly inserted into the expression. These are all the syntax checks that a syntax analyzer can perform based on grammar specification which is given to it. The output of a syntax analyzer is a tree this small tree is called as a syntax tree or abstract syntax tree. So, for example, here it shows the structure of the assignment statement above this was Fahrenheit equal to you know let us look at it Fahrenheit equal to centigrade into 1.8 plus 32. So, here this i d corresponds to Fahrenheit this i d corresponds to the next identifier and then we have the assignment symbol plus star 1.8 and 32. So, everything is working out well. So, this structure is shown in the form of a syntax tree which is the input to the next phase of a compiler called the semantic analyzer. So, syntax analyzers can be generated automatically from context tree grammars. So, we will learn about these specifications a little later, but right now it suffices to say that you know there are tools to do this. For example, the yawk and antler are two such tools. So, they handle what are known as you know L a l r 1 grammars that is yawk and bison and antler handles what are known as L L 1 grammars they generate c programs or c plus plus programs which correspond to these parsers you know and they can be used by the compiler. So, as I already said parsers are based on push down automata. So, they are actually what are known as deterministic push down automata and there is a reason why we need the next phase of analysis called semantic analysis. The reason is parsers cannot handle context sensitive features of programming languages let me give you few examples. So, if you are looking at the syntax alone that is whether the assignment statement has an identifier on the variable or identifier on the left hand side, a properly formed expression on the right hand side this forms a syntax, but if you say can I check whether an integer variable is being assigned a floating point expression that is type matching on both sides of assignment this cannot be handled by a parser. So, there are theoretical limitations here context free grammars cannot express such features of programming languages another feature which is here is variables are declared before use. So, we all know that we declare variables int a you know float b etcetera. Then at the time of using a and b that is suppose a is used in an assignment statement or an expression the compiler make sure that a was declared before and it also make sure that the type corresponding to the usage of a is the same as the type corresponding to it is declaration. So, this feature variables declared and then checked after use or even declared before use cannot be captured using context free grammars. They require higher form of grammars called as context sensitive grammars and this is a reason why we need another phase of analysis called semantic analysis. Another very important feature of programming languages which cannot be captured in the context free grammar and hence cannot be caught as mistakes of this kind cannot be caught by a parser they is here you know parameter types and number match in declaration and use. So, we declare a large number of parameters in program functions which we write and each one of these parameters has a type attached to it. So, when we actually call that function or procedure we have to make sure that the actual parameters that we supply or of the same type and the number of parameters we supply are exactly the same as the one in the declaration. So, parameter type and number match in declaration and use this property cannot be caught by the parser if the misuse of this property cannot be mistakes if this type cannot be caught by a parser and therefore, semantic analysis is supposed to take care of it. So, the next phase is the semantic analysis phase the input to the semantic analysis phase is a syntax tree. So, we already know that a syntax tree is produced by a syntax analyzer it goes into a semantic analyzer and the output is also a syntax tree, but it is actually modified syntax tree in other words there are changes made to this particular syntax tree. So, that the types of operands are all taken care of for example, this expression i d star 1.8 corresponds to a floating point type you know both the i d 2 and 1.8 are floating point types, but then we are adding an integer called 32. So, if there is some violation the you cannot really add floating point numbers and integers directly because the representation of these numbers is different inside a compiler. So, what does the compiler do inside a machine? So, the representation is different inside a machine. So, what does the compiler do? It converts the number 32 into a floating point number and then proceeds to generate you know machine code for this particular statement. So, and this is recorded faithfully in the syntax tree which is called as the annotated syntax tree. So, that the code generator need not worry too much it just goes ahead with code generation looking at what is available in the annotated syntax tree. Semantic consistency that cannot be handled at the parsing stage is handled here. So, I already gave you examples of this. So, I am in the same thing is repeated here type checking of various programming language constructs is one of the most important tasks. A semantic analyzer also stores information in the symbol table or the syntax tree itself. So, each node of the syntax tree could store information corresponding to that particular node. What is the type of information that is stored? What are the types of variables is it int, is it float, is it a struct, is it an array etcetera. What are the types of function parameters? What are the dimensions of an array etcetera. So, these are the information that are stored in a symbol table by the semantic analyzer. This information is used not only for catching errors semantic validation as we know it, but it is also used for subsequent phases of the compilation process itself. For example, the code optimizer will also require information about the types of operands in order to perform certain types of optimization and then the machine code generator needs to know the types of variables in order to generate appropriate types of instructions. So, both these phases require access to the symbol table. So, the semantic analysis phase builds the symbol table and that is the database which is used by the phases later in compilation. Static semantics of programming languages can be specified using what are known as attribute grammars. So, we are going to study attribute grammars also in our course a little later of course and attribute grammars actually are an extension of context free grammars. They are useful for specifying the semantics what are known as static semantics of programming languages and it is possible to generate the semantic analyzers semi-automatically from such specifications of attribute grammars. The next phase of compilation is the intermediate code generation phase. So, the annotated syntax tree which is output from a semantic analyzer is the input to an intermediate code generator and the output of the intermediate code generator goes to a machine independent code optimizer. So, let us see what this intermediate code generator has done on our example. So, here is a small tree corresponding to an assignment statement. So, it is very obvious that we need to do this multiplication first then the into float and then the plus and finally, the assignment. So, and that is the order in which the intermediate code has been generated. So, I will tell you why intermediate code after a few minutes, but let us understand this code to see what the intermediate code generator has done. It has generated t 1 equal to i d 2 into 1.8 corresponding to this expression. It has generated t 2 equal to into float 32 corresponding to this expression, t 3 equal to t 1 plus t 2 corresponding to this small tree and finally, i d 1 equal to t 3 corresponding to that assignment operator. So, an intermediate code program has the same semantics as the original source level. So, it is a machine level program, but it is at a much lower level compared to the source level program, but I must you know mention that it is not a machine language. So, let us see why we require such intermediate code and what exactly we do with it. So, when generating machine code from directly from source code is definitely possible. There is no theoretical or practical limitation. There are two problems associated with this approach. The problem is you need to write too many compilers. Suppose you want to write compilers for m languages and let us say you have n target machines for which you require compilers. So, if we directly write you know generate machine code without generating any other form of intermediate code, we need to write m into n number of compilers. Now, inside a compiler the code optimizer is perhaps one of the largest and the most difficult to write component and it so happens that if we write you know compilers which generate machine code directly, we will not be able to reuse any part of this optimizer. To give you the you know to some inkling of what is involved about 50 percent of the compiler source code is for you know the front end that is the lexical analyzer, the parser and assume semantic analyzer and let us assume that there is an intermediate code generator. So, all these four components together form about 50 percent of the source code of a compiler. The other 50 percent is for the code optimizer and the machine code generator. So, out of these about 30, 35 percent is meant for just this code optimizer and the other 20 percent, 25 percent is for machine code generation and machine code optimization. So, a very large part of compiler 30 to 40 percent, if it has to be rewritten again and again you know for every language and every machine it is a waste of a part. What we try to do is to generate intermediate code from the source code and then this intermediate code will be the same for many languages and many types of target machines. So, whether it is C or C plus plus or Fortran or Java the intermediate code will be very similar. So, we in fact for GCC the same type of intermediate code is used by the entire family of GCC GNU compilers really. GCC is one of them we have GNU compilers for Fortran, Java and C plus plus as well. So, all these compilers use the same form of intermediate code. Once we have the same intermediate code for many languages we can write a single machine independent code optimizer. So, in other words that 35 percent component is going to be used for different languages and it is a common module which will be used for different compilers as well. And of course, so once we do that we do not require m into n compilers, but we will really require m plus n compilers. So, for m different languages we require different the front ends that is lexical analyzer, parser etcetera etcetera and we also require n numbers of code generators which are specific to the various target machines, but the intermediate code optimizer is going to be common between these. So, strictly speaking you really require m plus n plus 1 number of components for the compilers. Intermediate code must be easy to produce it should not be as complicated as machine code, otherwise the effort spent in writing a machine code generator and machine independent or intermediate code generator will be similar. So, we do not want that to happen we want the intermediate code to be very simple and very easy to produce. This is some type of a universal language which can be mapped to any you know machine code and it should not contain any machine specific parameters no registers, no addresses etcetera etcetera. There are different types of intermediate code as well. So, the type of intermediate code that is deployed actually is based on the application. So, quadruples, triples, indirect triples, abstract syntax trees these are all classical forms of intermediate code. They have been in existence for decades and they have been used in commercial compilers machine independent optimization machine code generation in various types of compilers. That is something we are going to study later in our course. Then there is a form of intermediate code called the static single assignment form which is a recent one. When I say recent for the past 7, 8 years it is been deployed in the GCC compiler and using this type of optimized this type of intermediate code makes some of the optimizations more effective. For example, conditional constant propagation, global value numbering these are two very important optimizations which are carried out by a good compiler not necessarily simple compilers, but good quality compilers and these optimizations are more effective on an intermediate code such as SSA rather than the quadruples or triples. So, modern compilers nowadays invariably use SSA form as one of their intermediate forms. So, in other words we may end up using two or more types of intermediate code in our compiler to begin with it could be quadruples or abstract syntax trees. It may be translated to static single assignment form for better optimization and again translated to another type of intermediate code for better machine instruction machine code generation. Finally, program dependence type of a graph is another type of intermediate code which is useful in automatic parallelization instruction scheduling software pipelining etcetera. This is the intermediate code which shows the dependence between various types of statements in the program. For example, if there are two assignments statements in the program one of them produces a variable a the other one uses a variable a then there is a dependence between these two statements. So, this is in a nutshell what a program dependence graph shows. So, which this type of dependence is useful for automatic parallelization and other operations which I have mentioned here. Now, the code optimizer is the next phase which takes as input the intermediate code and generates you know produces very efficient code optimizer code intermediate code and inputs it into the machine code generator. So, I have already told you what a code optimizer is it improves code. So, let us see how it operates here. So, here the first statement t 1 equal to 82 star 1.8 remains as it is there is not much we can do in that, but it is not necessary to retain the second statement which is t 2 equal to into float 32. So, we might as well create a floating point constant 32.0 and generate a new quadruple I d 1 equal to t 1 plus 32.0 instead of the three quadruples which are stated here. So, we have actually reduced the number of quadruples from 4 to 2 you know it is a really good achievement because for the short program it implies 50 percent improvement. The machine independent code optimization actually becomes necessary because intermediate code as I said is a very simple type of code and the intermediate code generation process introduces many inefficiencies. So, there are extra copies of variables then instead of constants we actually put them into variables and then use that variable and then some expressions are evaluated again and again. So, these are all the inefficiencies which result from intermediate code generation. So, code optimization removes such inefficiencies and improves code and the improvement may be either time or space or power consumption. So, depending on what you require. So, for example, for very efficient servers you require time and memory optimization whereas, for embedded systems it could be power consumption which needs to be minimized. Code optimizers also change the structure of programs and sometimes they change it beyond recognition. So, they may inline functions, they may unroll loops. So, what does inlining of functions mean when there is a function call instead of making a subroutine call for that particular function. The code of that particular function is embedded into the program and that is called inlining. Unrolling loops of course, is easy and fairly well known. We do not execute a loop 100 times instead of that we may execute it only 10 times, but the body of the loop is made 10 times bigger. 10 iterations are actually inlined inside the loop and that is called unrolling of a loop and eliminating some programmer defined variables. So, if there is a counter called i and there is another variable j which is dependent on i in such a case it may be possible to remove i itself eliminate i. So, this is called induction variable elimination. Code optimization is actually a bunch of heuristics and the improvement may actually be just 0. You never know whether there would be improvement or not, but some programs yield improvement some other programs may not then yield any improvement. So, there are different types of machine dependent optimizations. For example, common spectrum elimination, copy propagation, loop invariant code motion, partial redundancy elimination, induction variable elimination and strength reduction, code optimization and to perform these optimizations we require information about the program. What type of information? Which expressions are being recomputed in a function? Which definitions reach a particular point? So, analysis of the program to determine such information and storing it in a particular way is called data flow analysis and we are going to study this part of a compiler towards the end of the course. Finally, the machine code generation. So, it takes intermediate code as input and outputs a particular type of machine code. In this case you can say in a load floating point and then multiply floating point, add floating point, store floating point corresponding to these two instructions are being generated here. So, it converts intermediate code into machine code and each intermediate code instruction may actually result in many instructions otherwise it is possible that many intermediate code instructions actually give rise to only one single machine instruction depends on the complexity of the machine. It must also handle all aspects of machine architecture registers, pipelining, cache, multiple function units, multiple cores whatever all these aspects must be handled by the machine code generator. Generating efficient code is a very difficult problem is usually NP complete and therefore, only for very simple types of machines this can be done optimally and generally tree pattern matching based strategies are among the best that are available. So, of course, we require tree intermediate code for this type of tree pattern matching based generation. Storage allocation decisions are also made here. So, you know register location which registers are used which operand should go into which register etcetera, etcetera are all solved in the machine code generation phase of the compiler. There are also after machine code generation even the code that results is not very efficient it is possible to improve it little more. For example, there are what are known as machine dependent optimizations a few are listed here. There are what are known as peephole optimizations. So, you analyze a sequence of instructions say 10, 15 in what is known as a small window and this window is called as a peephole and using preset patterns you replace them with more efficient instructions. So, for example, load a comma r 1 store r 1 comma a well you know we are loading and then storing immediately. So, this is not necessary. So, you could just say load a comma r 1 get rid of the store instruction. Sometimes there is no need to if you have a some code where there is a jump instruction and the target is another jump then this jump to jump can be eliminated and replaced by a single jump. It is possible to use a increment instead of load and add. So, these are called machine idioms and these form part of a peephole optimizations. Instruction scheduling that is reordering of instructions to eliminate pipeline interlocks and increase parallelism. So, usually we should not make the pipeline get stuck there should be a free flow into the pipeline and out of the pipeline. So, reordering instructions to make this happen is called instruction scheduling and that is one of the machine dependent optimizations and if the basic you know programs have what are known as basic blocks which are single entry single exit pieces of code. So, if they are very small there is no way you can increase the parallelism in the program. So, we must make them bigger and this technique called trace scheduling is used to increase the parallelism available in the program by increasing the size of basic blocks. Finally, the software pipelining which is too complex to explain orally is a very sophisticated optimization which is used to increase parallelism in loops. So, that brings us to the end of the overview and in the next lecture we are going to look at the details of lexical analysis parsing etcetera. Thank you very much.