CS 3721
Programming Languages  
Fall 2014
 Homework 8. Tiny Compiler: 
Assignments, etc.
Week 10: Oct 27 - 31

Submit following directions at: submissions and rules at: rules. Deadlines are:
  • 2014-11-05  23:59:59 (that's Wed,   5 Nov 2014, 11:59:59 pm) for full credit.
  • 2014-11-10  23:59:59 (that's Mon, 10 Nov 2014, 11:59:59 pm) for 75% credit.


Translating Assignment Statements to MIPS: Study the pages:

Overview: For this recitation you are to implement the following language features:

  • Constants that are a single digit.
  • Variables that are a single lower-case letter.
  • Outputting the value of an expression.
  • Outputting a newline.
  • Handling assignment statements.
  • Handling expressions that use the 4 binary operators: + - * /, along with parens.


MIPS Grammar for the above features: In red are features beyond what is in the R-D parser for Homework 7, although the operator ^ and unary minus are left out.

Tiny® Grammar: Assignments (red = additions)

 M  -->  S { S } '$'
 S  -->  A  |  P
 A  -->  lower-case '=' E ';'
 P  -->  '<' ( E  ';'  |  'N' ';') 
 E  -->  T {('+'  |  '-') T}
 T  -->  F {('*'  |  '/') F}
 F  -->  '(' E ')'  |  lower-case  |  digit 


Suggested order of work for you to do:
  1. Start with your R-D parser from Homework 7. Add code to this parser so that it will parse the red parts in the grammar above. In this case:
    • M stands for "Main program", will consist of a sequence of 1 or more statements.
    • There are only two kinds of statements:
      • assignments, which start with a lower-case letter and assign a double value to a lower-case variable.
      • output statements, which start with a '<' char. These either output the double value of an expression or output a newline.
    • Each statement ends with a semi-colon ';' char.
    • The whole program ends with a '$' char.

  2. Output a MIPS "framework" program that will surround all the compiled code. The framework includes one feature that will print "Executed by <your name>" when the MIPS is executed.

  3. Add to each of the functions E, T, and F features so that each one will return values from others that are called. All returned values will be None initially, but as the compiler is built up, they will all be memory addresses.

  4. Inside the function F, when the token for a digit, or the token for a lower-case letter is encountered, return the address in memory of that item as we have declared our memory. This is eliminating many of the returned Nones.

  5. Implement the output features of tiny, using the < operator, which is followed either by an expression (in which case you arrange for the MIPS code to output the double value of the expression), or an N (in which case you arrange for the MIPS code to output a newline).

  6. Implement assignment statements of the form <lower-case> = <lower-case> | <digit>

  7. Implement the four operators: + - * / [They're all in the same form.]


Each of the items above in order:

  1. Additions to parser: If you like, you can have zero or more statements in a program, so that $ all by itself is a legal program.

  2. "Framework" program: You should have your program print the following MIPS program, the first part before any other compiler output, and the second part after any other compiler output.

    Tiny® Framework MIPS Program
    % cat ex1.s
    # Compiled by Edward Blake
    main:   move    $s7, $ra
            la      $s1, M # data addr
    # Print your name
            li      $v0, 4
            la      $a0, Name
            syscall
    # Start of compiled code
    
    # All compiler output here; initially none
    
    # Stuff at end
            move    $ra, $s7
            jr      $ra  # ret to sys
    # data declarations
            .data
            .align  3
    M:      .double 0.,1.,2.,3.,4.,5.
            .double 6.,7.,8.,9. # cons
            .space  208  # a to z
            .space  1000 # 125 temps
    Blank:  .asciiz " "
    NewL:   .asciiz "\n"
    Tab:    .asciiz "\t"
    Name:   .asciiz "Executed by Edward Blake\n"
    
     
    Execution in an Elk with spim
    % spim ex1.s
    SPIM Version 7.4 of January 1, 2009
    Copyright 1990-2004 by James R. blah...
    All Rights Reserved.
    See the file README for a full blah...
    Loaded: /usr/lib/spim/exceptions.s
    Executed by Edward Blake
    % ...
    
    

    Inside the function M will be parser code to handle a sequence of 1 or more statements, followed by $. If you do it this way, you will have to supply a non-empty statement, such as a = 0; $, which the compiler will recognize, but will not translate. Instead it should print the framework program in two parts, putting the first part before the single statement above, and the second part after this statement.

    If the MIPS output is ex1.s, then you need to execute it using the spim simulator on any Elk machine or elsewhere. In the end, your MIPS program should print Executed by Edward Blake, but with your own name,

  3. Return values: Each use of E(), T(), and F() should be replaced by res=E(), res=T(), and res=F(). Here res is a variable that holds the result value returned by the function. [Of course you can use whatever variable name you want.]

    Also at the end of each of these functions, you should have return res. [This is the rough idea -- may need some sanity checks.] Thus each function returns values "up the line" as with synthesis in syntax-directed translation. [See the end of R-D Parsers.] Initially all of the values returned will be Python None.

    Eventually all values returned will be addresses where your MIPS program will find desired values or can store desired values at run time.

  4. Return values for a digit or a lower-case letter: In your compiler you can work with byte addresses, or addresses of doubles. If you use all addresses of doubles (as I did), then you must multiply each address by 8 before using it in a MIPS program.

    Thus the double with value 3.0 is at address 3 in addresses of doubles, or at 3*8=24 in bytes. The last is an offset in bytes from the start of M.

    Similarly, the double location used for the variable a is at address 10 in addresses of doubles, or at 10*8=80 in bytes. [See MIPS for formulas and a table.]

  5. Output using <: At this point, only the first 10 locations, corresponding to doubles 0.0 through 9.0 have values. Thus we can only handle statements such as: < 7;. Down inside the parser (the developing compiler), you will recognize the < char, scan past it, and then call E(). If you've handled the returns parts 3 and 4 correctly, then the address of the double 7, namely 56 bytes from the start of M will be returned. You need to spit out MIPS code to load this value into register $f12, and then output as we've seen with examples. Note that this part is exactly the example covered in the detailed presentation: Simple Example.

  6. assignment statements: You should implement assignment statements using a single MIPS load and a single MIPS store instruction in a way similar to the previous item. In order to test that the assignments are working correctly, you should compile several test programs, such as:

    Tiny program testing assignments
    % cat ex2.t
    a = 7;
    b = a;
    < 1; < N;
    < b; < N;
    c = 3;
    d = c;
    e = d;
    < 2; < N;
    < e; < N;
    $
    
    % spim ex2.s
    SPIM Version 7.4 of blah, ...
    Copyright 1990-2004 blah, ...
    All Rights Reserved.
    See the file README blah, ....
    Loaded: /usr/lib/ ...
    Executed by Neal Wagner
    1
    7
    2
    3
    
    

    Suppose the compiler output, a MIPS program, is called ex2.s. On the right above is the result of using spim to execute this program.

  7. Implement the four arithmetic operators: + - * /: In some ways this is the most challenging part, though it involves the same concepts that come up above.

      Do just plus(+) first, and focus on the following example. (Once you have + done, the other three operators are essentially the same.)

      Sample Input c:
      Input: g = f + 8 ; $
      Should produce output:
      # M[36] = M[15] + M[8]
              l.d    $f2, 120($s1)
              l.d    $f4,  64($s1)
              add.d  $f6, $f2, $f4
              s.d    $f6, 288($s1)
      # M[16] = M[36]
              l.d    $f2, 288($s1)
              s.d    $f2, 128($s1)
      

      You must add to the function E so that when there is an addition, it will produce two loads, add, and a store instruction. These are the first 4 MIPS instructions above. They are output from inside the E function.

      The final 2 instructions above handle the "assignment" part of the statement. They are output from inside the A function.

      If there is a +, then inside E, there will be two calls to T, each returning an address taking care of whatever in the tree is below that T. These two addresses are used for the two loads. Then you need to generate a new temporary memory location, and that will be the address in the store instruction. Finally, if there are only two operands in that call to E, the address of the temporary is what E will return.


      More about handling an addition: An addition was discussed and illustrated in Step 4 directly above. Let's go into it in more detail. Simplify the parser code so that it will just handle a single addition, without worrying about several of them. (In the actual code there is a while statement instead of the if below. This code is shown with C/Java syntax rather than Python.) This is the function on the left below. The code beside on the right shows E returning an int to hold an address, the offset from $s1.
      Function E for parser
      void E(void) {
      
         T();
         if (next == '+') {
            scan();
            T();
         }
      }
      
        
      Function E for compiler
      int E(void) {
         int res, arg1, arg2;
         res = T();
         if (next == '+') {
            arg1 = res;
            scan();
            arg2 = T();
            // MIPS code to achieve:
            // res = result of computing arg1 + arg2
         }
         res = // temp address for sum
         return res;
      }
      

      After E gets the two addresses from the two calls to T, it can output an add instruction. First the two operands from the two T's must be loaded. Then the add using registers, and finally a store instruction to stick the result into a new temporary memory location. The diagram below shows this. (Diagram in PDF.)


      New Temporaries: Each time there is the result of an arithmetic operation in MIPS, we need a new temporary location to store this result. The simplest way in Python is to use a global counter variable to keep track of the next one to use. With complex expressions it would be difficult to be sure there was no interference unless each new temporary is distinct from the previous ones. [See also Python Static Storage.]


Tiny programs to to compile to MIPS and run with SPIM.


    Sample Programs: Here are sample programs that you should compile from Tiny to MIPS assembly code. You then should execute the MIPS code. For these you need to implement the two types of output In order to successfully execute these using spim, you must add the stuff at the beginning and end of the complete sample programs. Of course all four programs work with doubles, but the doubles in Sample Input 1 are all exact integers.

    Sample
    Input 0:
    a = 8 * 2;
    b = 7 * a;
    c = b + 1;
    d = a / c;
    e = 3 + d;
    < e;
    < N;
    $
    
      
    Sample Input 1:
    f = 3*7;  
    g = f + (9 + 4);
    h = f + g;
    < h; < N;
    a = f*f + g*g; 
    b = g*h + f*g;
    c = g*g + h*h;
    < c; < N;
    f = a*a + b*b;
    g = b*c + a*b;
    h = c*c + b*b;
    < h; < N;
    a = f*f + g*g; 
    b = g*h + f*g;
    c = g*g + h*h;
    < c; < N;  $
    Output:
    55 4181 24157817
    806515533049393
    

    Fibonacci Nums: F10 F19 F37 F73
      
    Sample Input 2:
    < 3+1/(7+1/(5*3+1/(1+
    1/((4*(9*8+1))+1/(1+
    1/(1+1/(1+1/(2+1/(1+
    1/(3+1/(1+1/(2*7+1/(2+
    1) ))))))))))));$
      
    Sample Input 3:
    e = 2+1/(1+1/(2+1/(1+
      1/(1+1/(4+1/(1+1/(1+1
      /(6+1/(1+1/(1+1/(8+
      1/(1+1/(1+1/(2*5+
      1/(1+1/(1+1/(2*6+
      1/(1+1/(1+1/(2*7+1 
      ))))))))))))))))))));
    < e; < N; $
    


    What you should submit:
    • Give the complete Python source code for your compiler program.
    • For each of the four sample program in turn, include:
      • The original Tiny program.
      • The complete MIPS code that results from compiling that Tiny program, for Sample Input 0 only. (The MIPS code for the other 3 is pretty long, so just include the last 50 lines of the MIPS code, or some other segment of the MIPS if you wish.)
      • The output resulting from running that particular MIPS program using spim.

( Revision date: 2014-10-22. Please use ISO 8601, the International Standard.)