Advanced Code Gen for Classes

Bill Griswold
Adapted by Beth Simon

NOTE: The content of this outline is no longer maintained

Review from last couple classes

Compiling Classes: Records + Functions = Objects

Overview:

What does a "class" look like?

       RECORD Stack
         stk  : ARRAY 100 OF INTEGER;
         size : INTEGER;
         ...
       END Stack;

       PROCEDURE (S : POINTER TO Stack) pop();
         S.size := S.size - 1;
       END pop;

       PROCEDURE (S : POINTER TO Stack) init();
         S.size := 0;
       END init;

       VAR mystack : POINTER TO Stack;
       VAR mystack2 : POINTER TO Stack;

       VAR x,y : INTEGER;

       BEGIN
         new(mystack);
         new(mystack2);

         mystack.init();
         mystack2.init();

         mystack2 := mystack;   

         x := mystack.size;
         mystack.size := y; 

         mystack.push(3);
         mystack.push(7);

         IF (mystack.size > 5) & mystack.top() < 10 THEN
           mystack.pop();
         END IF;
       END.

Note:
            --------------------
            +mystack  mystack2 +
            +  +-+      +-+    +
         +-----+-|      |-+-------+    global
         |  +  +-+      +-+    +  |
         |  --------------------  |
         |  +                  +  |
         |  +        -------------+    text
         |  +        |         +
         |  ---------V----------
         |  +        oooooooo  +
         +---->ooooooo         +       dynamic
            +                  +
            --------------------
            +                  +
            --------------------
            +                  +
            +                  +       stack
            +                  +
            --------------------

        STE                      STE
   +-------------+          +--------------+
   |mystack      |          |mystack2      |
   |             |          |              |
   |gp      16   |          |gp       48   |
   +-------------+          +--------------+

Record Assignment: mystack2 := mystack;

Gee, the STE of a record holds an address where the record starts, kind of like an array STE does. Great, maybe the code I need for assignment for records is the same I need for arrays! But I control emitloadaddress and emitloadvalue by asking about isReference() for STEs and mystack is not of mode isReference()!

What do I need to have done in this case? What does the Statement Parse Rule already do? Answer: Exactly what we want it to do!

Note: Even though records and arrays are both have STEs that keep the addresses of the start of their structures, the fact that records are stored as pointers and arrays are marked as Mode:Reference helps us a great deal here! We, didn't have to make any changes to our code to get this to work!

Record Field Reference

Conceptually: What do I have to do to "access" mystack.size?
Answer: Something similar to arrays but this time "base" is the address stored in "mystack" STE and offset is sum of sizes of object components before "size"
          obj_ptr  = base(mystack) + offset(mystack)
          obj_base = *obj_ptr
          &(mystack.size) = obj_base + offset(size)

Parsing: The address is more powerful than the value!
No matter if mystack.size is on the right or the left of an assignment
Grammar rule is Des : Des . ID
This will look very similar to emitarrayref.

        $$ = newtemp();
        emitComputeFieldAddress($3, $1, $$); 


        void emitComputeFieldAddress(STE *field, *record, *result) {

          char *base_address_reg = get_reg();
          char *final_address_reg = get_reg();


          //Recall, emitloadvalue uses the base and offset of the STE
          //it is passed and stores the value at that address in the register
          //passed to it 

          //For a pointer to a record -- this result is the address of 
          //the start (or base) of the record pointed to
          emitloadvalue(record, base_address_reg);
          eprint("add %s, %s, %s", base_address_reg, 
                                   field->offset(),   
                                   final_address_reg);
          emitstorevalue(final_address_reg, result);

          //set type/mode of result so that emitstore, etc., knows what to do
          result->setType(field->type()->elemType());
          result->setMode(REFERENCE);

          freereg(base_address_reg);
          freereg(final_address_reg); 

        }         
This will make the assembly code for any access to mystack.size (on left or right side of assignment statement) look like:
                                ; value of mystack (pointer to Stack) is
                                ; in gp+16

                                ; Des : Des . ID  emitted by 
                                ; emitComputeFieldAddress
        ld  [gp + 16], r1       ; load pointer to mystack (emitloadvalue) 
        add r1, 400, r2         ; point to size within mystack (skip array)
        st  r2, [fp - 64]       ; store to T1 (emitstorevalue) (64 is made up)

Note: Record Offsets are slightly different from Array Offsets

Method Invocation

There are three problems to be discussed:

1) Keeping "hold" of a record STE inside a method call

Motivation: In order to use emitComputeFieldAddress to emit code for
       S.size := S.size - 1;
we need to have an STE that is returned for the lookup on S.

2) Nested/recursive method calls

At the time that we'd want to go ahead and put "mystack" as an extra argument to pop (or push), the compiler hasn't generated the function calls that are likely to appear in the argument list. In the case of
mystack.push(mystack2.top());
the mystack2 call as to be handled before the compiler can start emiting code for the mystack->push call.

As usual, we can use a stack to handle the nested/recursive nature of the constructs.
Note: The offsets stored for method STE's need to account for the fact that an extra argument has been stuck at the front. You could put it at the end, too, to avoid the problem.

3) Distinguishing between method calls and field references

Now that we feel we've created solutions to both our record field access problem (even inside a method call) and our method invocation problem we're done. Except how do we know when to use what solution?

The "Des . ID" syntax does not distinguish variable reference and type-bound procedure call, which are handled differently. You need to have an explicit test in the rule to distinguish the two cases and generate different code for them:
     Des : Des . ID { field_si := $1->lookup($3);  if (field_si->isProc()) ... }

Type-bound Procedure Naming

Dynamic Allocation

Lesson: Matching Solution Structure to Problem and Conceptual Integrity are Not Formulaic