CSCI 4627
Spring 2002
Answers to exam 2 questions

  1. How would a live variable analysis be useful in generating good quality executable code? A: It can help to avoid generating instructions to store some values from registers into memory. Note: Live variable analysis cannot allow more registers to be used, since it does not increase the number of registers. It does not obviate the need for control flow analysis, and it is not the right kind of dataflow analysis for detecting uses of uninitialized variables.

  2. Which of the following would be least likely to be stored in the symbol table of a compiler? A: the type of a complex expression. All of the others (the type of an identifier, the location where an identifier will be stored, the number of bytes required for a particular named type) are typically stored in a symbol table. They are characteristics of the things referred to by names.

  3. In a C- compiler, types of expressions can be computed: A: as synthesized attributes. The type information flows up the tree.

  4. Translators typically fall into one of two kinds. The first kind is like the translators that you wrote, performing actions during the parse that write out the translation. The second kind instead builds up syntax trees during parsing, and then passes those syntax trees to a separate semantic section of the compiler for translation. Which of the following is {\bf not} an advantage of the first approach over the second.

    A: Compilers written using the first approach are generally more flexible and easier to modify. In fact, compilers written using the first approach are generally quite limited in how they can perform translations, since they produce the translation in the same order as what they see in the parse tree. Changing the order can involve a lot of trickery.

  5. In a recursive descent compiler, an inherited attribute typically shows up as: A: a parameter to a function.

  6. In a recursive descent compiler, a synthesized attribute typically shows up as: A: A value returned from a function. Note: sometimes a synthesized attribute is returned through an out (reference) parameter. That is just another mechanism for returning a value from a function.

  7. Assume that a and b are global variables of type int, where a is at offset 1 and b is at offset 2. Write a translation of expression a + b + b into code for the abstract machine used in your compiler project. The code should compute the value of this expression and store the result onto the stack.

        M_FETCH_GLOBAL_INTEGER
        1
        M_FETCH_GLOBAL_INTEGER
        2
        M_INTEGER_ADD
        M_FETCH_GLOBAL_INTEGER
        2
        M_INTEGER_ADD
      

  8. In C, the for statement has the following form.

        for ( e1 ; e2 ; e3 ) stmt
      
    and has the same meaning as
         e1;
         while ( e2 ) {
           stmt;
           e3;
         }
      
    A for loop is described by production
        for-statement -> for ( expr ; expr ; expr ) statement
      
    Write attribute equations to produce the code for these expressions, using the byte code that your C- compiler targets. Assume that an expression e has an attribute e.code that is a sequence of instructions that will compute the value of e and push its result onto the stack. (For this exercise assume that Boolean values are pushed on the stack, with 0 standing for false and 1 standing for true.) Similarly, a statement s has attribute s.code that performs statement s, leaving nothing extra on the stack. Give an equation for for-statement.code. Use the following functions as helpers.

    Production: for-statement -> for ( expr1 ; expr2 ; expr3 ) statement

    Semantic actions

        int L1 = newlabel();
        int L2 = newlable();
        for-statement.code =
          expr1.code + 
          byte(M_POP) +
          byte(M_LABEL) + 
          byte(L1) +
          expr2.code +
          byte(M_GOTO_IF_ZERO) + 
          byte(L2) +
          statement.code +
          expr3.code + 
          byte(M_POP) +
          byte(M_GOTO) + 
          byte(L1) +
          byte(M_LABEL) + 
          byte(L2)
      

  9. You are given the following grammar for variable declarations.

        declaration    -> type identifierList
        identifierList -> identifier
        identifierList -> identifier identifierList
      
    for example, declaration
        int x,y,z;
      
    declares x, y and z to have type int. The lexical analyzer produces an attribute called name for an identifier, and the parser produces an attribute called typ for a type. A function insert(name,type) is available that makes an entry in the symbol table indicating that the type of variable name is type, where name is a string and type is the same kind of thing that is the typ attribute of the type nonterminal.

    Write attribute equations and actions that make all of the symbol table entries called for by a variable declaration. For example, declaration int x,y,z; should produce three symbol table entries, one for each of x, y and z. Assume that the action will be performed only after all of the attributes of a node have been computed. You may use inherited attributes or synthesized attributes or any combination of them.

    Note: The grammar is missing the commas that are shown in the example. That is a minor issue. I will ignore the commas.

    The problem is to get the type to the places where it needs to be used. But that can be done by passing the type down the tree as an inherited attritute.

        declaration    -> type identifierList
             identifierList.type = type.typ
    
        identifierList -> identifier
             insert(identifier.name, identifierList.type)
    
        identifierList -> identifier identifierList1
             identifierList1.type = identifierList.type;
             insert(identifier.name, indentifierList.type);