CS536 Homework 3
Fall 2018
Due by 11:59 pm on Friday, December 7, 2018 (not accepted late)

Questions

Homework assignments must be done individually. Collaboration on homework assignments is not allowed.

In project 5 we are generating JVM code for all the CSX components. For an expression rooted at AST node n, a call to CodeGenerating.visit(n) generates code to evaluate the expression, leaving the result at the top of the JVM's operand stack.

While this kind of code is easy to generate, storing intermediate values on the stack rather than in registers can be inefficient. Almost all modern architectures, including Intel's ubiquitous x86, provide hardware registers. Expressions can be computed directly into registers, avoiding use of stack locations.

You might think we could instead require that the visit methods for an expression should work as follows:

  1. Each expression node's visit method (in CodeGenerating) would have 1 parameter: a register number N, which would be either 0 or 1.
  2. For literals or identifiers, the visit method would simply load the appropriate value in register N.
  3. For expressions involving non short-circuited binary operators (+, *, <, etc.) the visit method would call the visit method of the left child with argument 0 (which would generate code to evaluate the left expression, leaving the result in register 0), then call the visit method of the right child with argument 1 (which would generate code to evaluate the right expression, leaving the result in register 1), then perform the operation, leaving the result in the appropriate register (either 0 or 1).

Unfortunately, this approach does not always work.

Question 1:

Assume that the AST includes only expressions that involve non short-circuited binary operators, with literals or identifiers at the leaves (no unary operators or function calls as operands). Also assume that in the generated code, all operands must be in registers (i.e., neither operand of a binary operator can be in a memory location, nor can it be a literal value).

For the following examples, judge whether the approach described above works. If not, show pseudo-code that uses the least possible number of registers needed to evaluate the expression. Provide the AST of expression and a pseudo-code version of the code to evaluate the expression (code that works, not the erroneous code that would be generated using the approach described above). If the approach described above works, you can just answer "Yes"; you do not need to show the AST and pseudo-code. You can assume that no optimization is done prior to code generation.

  1. (a-b) - c

  2. (a-b) - (c-d)

  3. ((a - b) - (c - d)) - ((e - f) - (g - h))

Here is a example of AST and pseudo-code for the expression a-b (use the same kind of pseudo-code in your answer):

Expression AST pseudo code
a - b
  -
  / \
  a   b 
  load a into T0  
  load b into T1  
  T0 = T0 - T1

Note that an expression's operands need not be evaluated left-to-right. For example, the following pseudo-code would also be OK for the expression a - b:

load b into T1    // evaluate the right operand first
load a into T0
T0 = T0 - T1

Question 2:

Use the same assumptions about the AST and the generated code as for Question 1, including the fact that an expression's operands can be evaluated either left-to-right or right-to-left. Also assume that all operators are non-commutative and non-associative (i.e., the expression a op b is not equivalent to the expression b op a, and the expression (a op b) op c is not equivalent to the expression a op (b op c) ).

You are to give a recursive algorithm that works as follows:

  • The input to the algorithm is an expression node in the AST (recall from Question 1 that we are assuming that we only have expressions involving non short-circuited binary operators).

  • The output of the algorithm is the minimum number of registers required to generated code for the whole expression.

    Note that generating code to evaluate a leaf node requires one register (into which the value of the identifier or literal is loaded).

    For non-leaf nodes, your algorithm should recursively calculate the number of registers required to generate code for the left operand (leaving the value in a register) and the number of registers required to generate code for the right operand (leaving the value in a register) and then determine the number of registers required for the entire expression represented by the node.

In other words, complete the following method:

int numRegisters(ASTNode node) {
    // add code to calculate and return the number of
    // registers required to generate code for the whole
    // expression (whose root is node)
}

You may use pseudo-code such as node.leftChild, node.rightChild, max( , ), min( , ), and isLeaf( ).

For example, for the expression a - b, the input to the algorithm is the root node of the expression tree. The algorithm will recursively determine that the number of registers required to generate code for each operand is 1 (because each operand is a leaf and thus requires one register). The output of the algorithm for this example should be 2 (because the whole expression can be evaluated using two registers, as illustrated above in Part 1, but it cannot be evaluated using just one register).

Handing in

Please include your name at the top your file.

Put your answers to all questions into one file named Homework3 with the appropriate file extension, e.g., Homework3.pdf

Electronically submit your work to the Homework 3 tab on Canvas.

Last Updated: 8/2/2018     © 2018 Charles Fischer