CSCI 5220
Spring 2009
Practice Questions for Exam 2

  1. What is the difference between a synthesized attribute and an inherited attribute? Define each of those terms.

    Answer

  2. When you see a definition of an attribute, how can you know whether the attribute is synthesized or inherited just by looking at the definition and the production?

    Answer

  3. You are given the following (ambiguous) grammar for expressions.

          expr -> expr + expr
          expr -> expr * expr
          expr -> NUM
          expr -> VAR
      
    where NUM and VAR are tokens. The lexer provides an attribute NUM.val that is the (integer) value of a NUM token. It also provides an attribute VAR.name that is the name of a variable (a string). You would like to translate these expressions into instructions for a stack machine. The stack machine has the following instructions.

    PUSH_INT k Push integer k onto the stack
    PUSH_VAR k Push the value of the variable at offset k onto the stack
    ADD Pop the top two numbers from the stack and push their sum
    MULT Pop the top two numbers from the stack and push their product

    The PUSH_INT instruction can handle any integer that the lexer will produce as an attribute of a NUM token. You have access to three support functions: get_var_offset(v) returns the offset where the variable named v is stored; gen1(I) generates single-part instruction I, and gen2(I,k) generates two-part instruction I, with parameter k. (For example, MULT is a single-part instruction, since it has no additional information, but PUSH_INT is a two-part instruction since it contains the integer to push.)

    Write semantic actions to be performed at each production that will generate code to compute a given expression and leave its value on the top of the stack. Do not worry that the grammar is ambiguous. That is a parsing problem, not a semantic one. Write the actions to generate the code as the parsing is done. That is, write a narrow translator.

    Answer

  4. This is the same as the preceding exercise, but instead of performing actions to generate the code, you would like to create the code sequence as an attribute of an expression nonterminal, so that you end up with a wider translator. Suppose that, in addition to get_var_offset(v), the following functions are available. single(I) produces, as its value, a sequence that represents the single-part instruction I. doub(I,k) produces a code sequence for a two-part instruction. Operator + can be used to compute the concatenation of two code sequences.

    Answer

  5. Exercise 6.6.1(a) (p. 408 of the text). That is, give translation rules using the BE.false and BE.true attributes for translating repeat S while B, which means the same thing as C++ or Java statement do S while (B). Translate into a sequence of 3-address codes in a style similar to the one used in the book in Figure 6.36.

    Answer

  6. Suppose that your parser works by building abstract syntax trees for expressions and statements, generating code for them later. How can you handle exercise 6.6.1(b) without writing any extra rules for generating code?

    Answer

  7. Dataflow analysis is used to determine

    1. the relationship between places where variable values are used and where they are defined.
    2. where a program is in a loop.
    3. which parts of a program are good candidates for trying to keep variables in registers.

    Answer

  8. What is the definition of a basic block?

    Answer

  9. How would a live variable analysis be useful in generating good quality executable code?

    1. It can help to avoid generating instructions to store some values from registers into memory.
    2. It allows you to use more registers to hold temporary values.
    3. A live variable analysis makes it unnecessary to perform a control flow analysis.
    4. A live variable analysis is useful in finding places where variables might be used before they are defined.

    Answer

  10. Some languages are lazy; they defer evaluations of expressions until the value is needed. (In some cases, the value is never needed, and so the expression is never computed.)

    A property that is useful for compiling a lazy language is strictness. An instruction in a flowgraph is strict if the variable that it computes will surely be used later, no matter what happens. The program can perform a strict line immediately without violating the rules of lazy evaluation, and doing that is usually more efficient.

    A big part of determining which instructions are strict is determining which variables are strict. Say that a variable is strict at a place in a flowgraph if its value will surely be used later. Define strict-in[B] to be the set of variables that are strict at the beginning of basic block B, and define strict-out[B] to be the set of variables that are strict at the end of block B.

    Write dataflow equations that define strict-in[B] and strict-out[B] for each basic block B in a flowgraph.

    Answer

  11. How would you compute strict-in[B] and strict-out[B] for every block B in a flowgraph? Sketch an algorithm.

    Answer