Fall 1996
   What is Compiler?
   Compiler Vs Interpreter
   When Interpreter?
      - To execute command lang's
      - When info about data is known only at runtime (eg. arrays in APL)
      - If number of runs are one or few
   Parts of compilation
      o Analysis
      o Synthesis
   Analysis phases
      1. Lexical Analysis
      2. Syntax Analysis (Parsing)
      3. Semantic Analysis (Type checking)
   Parse tree Vs Syntax tree
   Phases of a compiler (p. 10)
                           Lexical Analyzer
                                 \/
                           Syntax Analyzer
                                 \/
               /          Semantic Analyzer           \
              /                   \/                   \
       Symbol --     Intermediate Code Generator      --  Error
       Table  \                  \/                    /  handler
               \           Code Optimizer             /
                                 \/
                           Code Generator
                                 \/
                           Target Program
   Cousins of compiler
       1. Preprocessor
       2. Assembler
       3. Loaders and Link-editiors
   Front End Vs Back End
   Compiler construction tools
       1. Parser generators
       2. Scanner generators
       3. Syntax-directed translation engines
       4. Automatic code generators (from intermediate lang)
       5. Data-flow engines (for optimization)
    Terminals, nonterminals, productions, tokens.
    Context-Free Grammar (CFG), Language (token strings derived from S)
    Parsing
       - process of finding a parse tree for a string of tokens
       - process of determining whether a string of tokens can be
         generated by a grammar.
       Time: worst - O(n^3); typical - O(n)
    Parse tree (eg. 9-5+2, p. 28)
    Ambiguous grammar - more than one parse tree for a token string
    eg. 1. S -> S + S | S - S | a
        2. S -> S S + | S S - | a      (ambiguous?)
    postfix. Why postfix?
        - only one interpretation
        - no parentheses needed to disambiguate
    Parsing types
       - Top down (start from start symbol and derive string)
         o Efficient hand-written parsers
       eg. Recursive-Descent, Predictive
       - Bottom up (start from string and reduce to start symbol)
         o Handles larger class of grammars
         o Used by parser generators
       eg. LR parser
    Recursive-descent
       - 1 proc for each nonterminal (NT). Call proc if RHS has the NT.
      problems:
       1. Backtracking
       2. Left recursion
    So, Predictive parsing
       - no backtracking
       - depends on first symbols generated by RHS of production. (first
         symbols should be distinct within a production)
       - decide production to use based on lookahead symbol
       - NT on RHS? call the corres proc
         T on RHS? read next token
      problems:
       1. Left recursion
    Eliminating left recursion
       A -> Aa | b
         transformed to
       A -> b | bR
       R -> aR | epsilon
   Why separate lexical analysis phase?
      1. Simpler lang design
         parser processing comments and whitespace is more complex.
      2. Improved compiler efficiency
         special techniques for lexing
      3. Improved compiler portability
   Regular Expressions
      r & s are reg exp's. r+s, rs, r*, r?, r+
      char classes [abc], [a-z]*, etc.
   Limitations of reg exp's
      1. Can't use for anything that involves matching
         - balanced paren's
         - repeated strings { wcw }  (type-checking problem?)
      2. Can use only for fixed or unspecified number of repetitions
   lex - Constructs a lexer from a lang spec (in reg lang)
   Steps in lex implemetation.
      1. Read input lang spec
      2. Construct NDFA with epsilon-moves (Can also do DFA directly)
      3. Convert NDFA to DFA
      4. Optimize the DFA
      5. Generate parsing tables & code
   lex file format
      declarations
      %%
      translation rules
      %%
      auxiliary procedures
   lex eg. on p. 109
   FA - Used to construct recognizer that recog. sentences in the lang
   NFA - slower but smaller recognizers
   DFA - faster but bigger recognizers
   NFA to DFA
   reg exp to NFA - Thompson's construction
   CFGs specify the lang syntax
   Why grammars to specify lang syntax?
      1. Grammar gives a precise spec of lang
      2. Automatic construction of parser from grammar
      3. Detects lang ambiguities & difficulties (useful in lang design)
      4. Imparts structure to lang (helpful in translation & error detection)
      5. Addition of new constructs is easy (helpful in lang evolution)
   Parser - takes tokens from lexer and builds a parse tree
   Types of parsers
      1. Universal (CYK, Earley)
         o inefficient
      2. Top-down (LL)
         o simple
         o suitable for hand-written parsers
      3. Bottom-up (LR)
         o complex
         o covers a larger class of grammars
         o suitable for automated parsers
    Parse trees & derivations
      - left most
      - right most
    Ambiguous grammar - more than 1 left-most or right-most derivation
    Why reg exp's over CFG's for lex spec's? (p. 172)
      1. Lex rules are simple. For this, reg exp is sufficient; power of
         CFG not needed
      2. Reg exp's provide concise & easier to understand notation for
         tokens.
      3. More efficient lexers can be constructed from reg exp's than
         from grammars
      4. Separating syntax into lexical and non-lexical parts makes
         compiler manageable
    Reg exp uses: specifying syntax for id's, const's, keywords, etc.
    Grammar uses: nested structures, if-then-else, etc.
    if-then-else disambiguation
      grammar:
       stmt ->  if expr then stmt
              | if expr then stmt else stmt
      input: if expr1 then stmt1 if expr2 then stmt2 else stmt3
      'else' goes with first or second 'if'?
      Solutions:
        1. Modify grammar
           stmt -> m_s | u_s
           m_s -> if expr then m_s else m_s
           u_s -> if expr then stmt
                  | if expr then m_s else u_s
        2. Always match else with nearest if (shift)
           - default in yacc
     Left recursion elimination   eg. on p. 176
        - immediate left recursion
          eg.  A -> Aa
        - non-immediate (?) left recursion
          eg.  A -> Bb
               B -> Aa
        o both need to be eliminated for use in top-down methods
        o bottom-up (eg. yacc) can accomodate left recursion
     Left factoring
        - done to produce unique first symbols on RHS of production.
          (so as to make it suitable for predictive parsing)
        eg. (p. 178) { LL(5) needed? }
           S -> iEtS | iEtSeS | a
           E -> b
         transformed to
           S -> iEtSS' | a
           S'-> eS | epsilon
           E -> b
     Top-down parsing (LL(1) grammar)
        1. Recursive-descent
           - requires backtracking
           - left recursion in grammar not permissible
        2. Predictive
           - no backtracking
           - left recursion not permissible
           - first symbols should be distinct (do left factoring)
        3. Non-recursive predictive
           - explicit stack
     LL(1) - First L: Read from Left to right
             Second L: Apply Left-most derivation
             (1): 1-symbol lookahead
     LR(1) - L: Read from Left to right
             R: Use Right-most derivation for reduction
             (1): 1-symbol lookahead
     Bottom-up parsing
         - Reducing a string to the start symbol of grammar
         eg. p 199
     Methods
         LR parsing (most general)
            Shift-reduce parsing
               Operator precedence parsing (least general)
     shift/reduce conflict
         - to shift or to reduce?
         eg. if
     reduce/reduce conflict
         - more than 1 NT to reduce to. Which one to reduce to?
     Both conflicts imply ambiguity in grammar. But an LR grammar can
     never be ambiguous.
     LR Parsers are the most general & thus the most complex of all
     bottom-up parsers. Then,
     Why LR parsers?
         1. Recognizes a large class of CFGs.
         2. Most general non-backtracking shift-reduce parsing method
         3. LR grammars are proper superset of predictive grammars
         4. LR grammars detect syntactic errors as soon as it is
            possible to do so.
       Disadv of LR parsers:
         1. Difficult to construct by hand
            - need LR parser generators
     Methods for construction of LR parsing tables.
         1. Simple LR (SLR)
         2. Look Ahead LR (LALR)
         3. Canonical LR
     LR grammars are proper subset of CFGs. This is not a problem since
     most Prog lang's can be represented by LR grammars.
     Difference between LL(k) and LR(k) grammars. (p 221)
     Ambiguous grammars
         - some time we use them. Why?
             o shorter, natural spec for some lang constructs (eg. expr's)
             o isolate common constructs for special case optimization
     Yacc - Yet Another Compiler Compiler - an LALR parser (p. 257 onwards)
     file format
         declarations
         %%
         translation rules
         %%
         supporting C routines
       yacc eg. p. 258
    Attribute grammars
        - useful in intermediate code generation
    Synthesized Vs Inherited Attributes  (p. 281)
    Syntax trees (condensed form of parse tree)
    Directed Acyclic Graph (DAG) for expressions
    Static Vs Dynamic type checking
    Structural equivalence  (p. 356)
       - two types are equal if their structures are equal
    Name equivalence
       - two types are equal if the type names are same
    Type Conversion
       - Implicit (coercion)
       - Explicit
    Overloading & polymorphic functions
    Type variables  (p. 366)  (templates?)
    Memory Layout
         -----------
            Code
         -----------
         Static Data
         -----------
            Stack
         -----------
             \/
             /\
         -----------
            Heap
         -----------
    Activation Record (AR) or frame
        - Info needed by a single execution of a procedure (p. 398)
    AR fields
       1. Returned value (to the caller)
       2. Actual parameters (from the caller)
       3. Optional control link (points to caller's AR)
       4. Optional access link (points to AR of proc in enclosing scope)
       5. Saved machine status (PC, regs)
       6. Local data
       7. Temporaries
    Maintaing ARs - division of tasks between caller & callee
       fig 7.14 on p. 407
    Access to non-local names
       - static scope: use access link in AR
       - dynamic scope: use control link in AR
    Techniques for accessing non-local names
       1. Static chains
          o follow access links thru ARs till you hit on the name.
       2. Display - array of pointers to ARs; array size corresponds to
                    number of nesting levels
          o To find a name at nesting depth i, look in the activation record
            pointed to by the display element d[i]
          eg. on p. 421
    Parameter passing methods
       1. Call by value
       2. Call by reference (aka call-by-address, call-by-location)
       3. Copy-Restore (aka copy in copy out, value-result)
       4. Call by name (macro?)
    Symbol Tables:
       - used to store type, scope, storage, etc. of a symbol. If the
         symbol is a proc name then,
           o number & type of args
           o method of arg passing
           o type returned
         are also stored.
       Static scope: compiler maintains symbol table
       Dynamic scope: program needs to maintain?
     Hashing (and thus Hash tables) is commonly used for symbol tables
     Representing scope info in symbol table - insert in front of chain
       as you enter a new scope.
     Dynamic Storage
        1. Allocation
           a. fixed-size blocks
           b. var-size blocks
        2. Deallocation
           a. Reference counts    -|
           b. Marking Techniques   |-> Garbage collection
    Machine-independent
    Why intermediate code?
       1. Can retarget for a different machine by changing the back-end
       2. Machine-independent code optimizer can be applied
    Intermediate representations
       1. syntax trees (DAGs)
       2. postfix notation
       3. 3-address code     x := y op z
    Boolean expressions - short-circuit evaluation
    'case' statement translation
       1. Series of if's
       2. Nested if's
       3. Branch table
    Backpatching
       - used in one-pass compiler/assembler
    Input choices
      - linear rep's
        1. postfix
        2. 3-address code
      - virtual machine rep
        3. stack machine code
      - graphical rep
        4. Syntax trees
        5. DAGs
    Output choices
        1. Absolute machine lang
        2. Relocatable machine lang
        3. Assembly lang
    Issues in the design of code generator
        1. Input to code generator
        2. Target programs (output choice)
        3. Memory Management
        4. Instruction Selection
        5. Register Allocation, etc.
    Transformations on basic blocks (kinda optimization)
        A. Structre-preserving
           1. Common subexpression elimination
           2. Dead-code elimination
           3. Renaming temp variables
           4. Interchange of 2 independent adjacent statements
        B. Algebraic
    Peep-hole optimizations
        - optimize a small window (peephole) of code (not nessarily
          contiguous)
        - chain effect: doing one peep-hole optimization uncovers other chances
          for peep-hole optimizations. Usually run the algorithm thru the code
          several times to optimize it fully.
      Examples of peep-hole optimizations:
        1. Redundant instruction elimination
           o loads & stores
           o unreachable code
        2. Flow-of-control optimizations
           eg. multiple goto's to single goto.
        3. Algebraic simplifications
           eg. X + 0 to X; X * 1 to X, etc.
        4. Strength reduction
           - convert costly (time-wise) operations to less costly operatios
           eg. X^2 to X * X
        5. Use of machine idioms (special machine instructions)
           eg. auto inc/dec while addressing memory
    A. Function-preserving transformations   (p. 592)
       1. Common subexpression elimination
       2. Copy propagation
       3. Dead-code elimination
       4. Constant folding
    B. Loop optimizations   (p. 596)
       1. Loop-invariant code motion
       2. Induction-variable elimination
       3. Strength reduction
    Beyond this, in the text...
       stuff you need to know to get a PhD in compilers :)