Procedures - CS 524 - Spr 08
21 April 2008

Now that the semester has progressed, I feel the groups can proceed using "suggestions" from the instructor. You may well have better ideas on how to implement these final Phases of the compiler project and I would like to hear from you. There are no deadlines - except the final project is due at the end of Finals Week (Friday 17May2008 Midnight)
Detailed example based on Last week lecture
This file include the source code for procedure with local variables, the SPIM code using s0 (globals), s1 (level 1 proc), s2 (level 2 proc), s3 (level 3 proc) for using Displays to handle nesting, and the execution within SPIM.
SPIM codegen hints
Suggested Structures for Translation
Symbol Table
Semstack with new Structures
Simple test file with Semantic Actions
New Statement Structure for Procedure Call
Concerns for the Forward Reference

The following is a grammar was abstracted from the adayacc.lr grammar which was made available on the class home page (under ADA-YACC. It is the full grammar for the entire Ada language (and may be confusingly long).

NOTE: This grammar is set up to handle the passing of parameters, which is not what is required in the class for the next phase. I wanted to give those students who are anxious to get going on additional phases the big picture here. Please do not forget to add the lr_debug statements so that you can continue to have this useful information produced to help with the development and understanding of this project.

body           : Body bodydecllist Begin stmtlist
bodydecllist   : bodydecllist typedecl
               |
                 bodydecllist procdecl
               |
                 lambda

procdecllist   : procdecllist procdecl
                         {mergeprocs();}
               | 
                 lambda {nullproc();}

decllist       : decllist decl
               |
                 decl

decl           : idlist Colonsym type Termsym
                         {enteridlist();}

procdecl       : procdeclhead Begin stmtlist End id_option2 Termsym
                         {endprocbody();}
               |
                 subprog_spec Termsym
                         {endnullprocbody();}

procdeclhead   : subprog_spec Is decllist procdecllist
                         {merge_sub_procs();}

subprog_spec   : subproc_head identifier formal_opt
                         {startproc();}

subproc_head   : Procedure
                         {bump_scope();}

formal_opt     : formal_part
               |
                 lambda
                         {nullparamlist();}

formal_part    : Lparen param_decl_list Rparen

param_decl_list: param_decl_list Termsym param_decl
                         {mergeparamlist();}
               |
                 param_decl

param_decl     : idlist Colonsym mode_option type
                         {decl_paramlist();}

mode_option    : mode
               |
                 lambda {pushint(0);}

mode           : In out_option
                         {set_in();}
               |
                 Out
                         {set_out();}

out_option     : Out
                         {set_out();}
               |
                 lambda
                         {set_notout();}

call_statement : name Termsym
                         { makeproccall();}

name           : identifier name_suffix_list

name_suffix_list: Lparen expr_list Rparen
               |
                 lambda
                         {nullarglist();}

expr_list      : expr_list Commasym arg
                         {mergearglist();}
               |
                 arg

arg            : expression
                         {createarglist();}

id_option2     : Identifier { checkname2(); }    
	       | 
		 lambda

Structures for Translation


Now let's take a look at the structures we need to build for the translation process. As with control structures, You will have a separate handout on the generation of SPIM code. This will focus on the semantics, so once you get your AST out correctly you will have used the ideas of this handout.

This document will not focus on the parameters.

So what do we need to keep track of for procedures?

  1. static nesting level
  2. local symbol table
We need a convenient way to search the symbol tables that the current routine is "contained in" when we are in the routine

idtoaddr

and this is where having a global scope_array was very convenient. So in sem.h your project has always defined
static int cur_proc_level = 0;              <----- this is already there
new constructs are:
/* for nesting level for procedures */
static int cur_proc_label = 0;              <----- this is for codegen
static SYMTABPTR scope_array[4];            <----- this is the scope-stack
We have the global variable

Root

which is always used by Lookup and Insert to access the symbol table. There will be several times when we need to search beyond the "current symbol table" and this means we must save the value of Root, reset Root from the scope_array and do the search using Lookup/Insert, and then after the search has been performed, restore the value of Root.

Of course a better idea would be to add a parameter to the Insert/Lookup call to pass the "Root" of the particular tree you want searched.


*** in idtoaddr ***
idtoaddr needs to search for an "identifier" name in all the appropriate symbol tables. First search in scope_array[cur_proc_level], if "identifier" is not found, then look in scope_array[cur_proc_level-1]. Continue until you have searched scope_array[0] (which is main's table).


*** in InitSemantics ***
After you set up the type names for "integer" (and other possible types such as "boolean") you want to store Root in scope_array[0].


*** no longer check for uninitialized vars *****
Since we do not know which procedures will be called at run time, and it's likely that some procedure might initialize a global variable, or some variable that is "visible" to it, we need to remove testing for uninitialized variables because it can't be done reasonably. This means you don't want to bother setting variables to be "used" or "initialized" and de-activate the test for an uninitialized variable which used to turn-off code generation.


Our global structure that ends up holding the final AST will have an additional field for a list of procedures:
struct programtree{
     STMTLIST main;
     VARLIST vars;
     PROCLIST proclist;
     struct constlist *const_listptr;
} Prog;
where PROCLIST is declared as
typedef struct prlist {
     IDNAME id;               /* procedure's name in characters */
     ATTRPTR thisproc;        /* pointer to proc's name in symtab */
     ATTRPTR oldcurrentproc;  /* pointer to old current procedure */
     STMTLIST procstmts;      /* pointer to stmts in body of proc */
     struct prlist *procfirst,*proclast,*next;     /* proclist */
} PROCDEF, *PROCLIST;
#define proc_alloc() (PROCLIST) (malloc(sizeof(PROCDEF)))
Notice, there are several lists being created here. Procedures that are declared at the same static nesting level need to be linked up through the "next". Procedures that are subordinate to this procedure, i.e. contained in this procedure, are linked up through the "procfirst" and "proclast" list.

As before, done() will set the fields of Prog

/* -------------------------------------------------------------------------- */
/* ---------------- DONE:  PARSING COMPLETED, TIE UP LOSE ENDS -------------- */
/* -------------------------------------------------------------------------- */
/* PRODUCTIONS:
 *   pgm ::= stmtlist #done
 */
/* SEMANTIC STACK MANIPULATION:
 *      top ->| stlstentry|
 *            ------------
 *            |prlistentry|
 *            -------------           -----------
 *            |    ?      |    top -> |    ?    |   (should be empty now)

           {set the values of Prog.proclist from Semstack[Semtop-1]
                              Prog.main = Semstack[Semtop].se.slist.first
                              Prog.vars = GetVars();
           }

Symbol Table


The symbol table now holds 2 different kinds of entries -
  1. varentry - for variable names and loop names
  2. procentry - for procedure

Since they involve different kinds of information, I created a union of the structures

/* ---------------------------------------------------------------------- */
/* ---------------- TYPE DEFINITIONS FOR THE SYMBOL TABLE --------------- */
/* ---------------------------------------------------------------------- */
typedef short ENTRYTYPE; /* types of entries in symbol table */
#define varentry      1
#define procentry     2
#define fwdprocentry  3

typedef struct symtabrec *SYMTABPTR; /* needed for forward decl of SYMTABREC*/

typedef struct attrec{
        IDNAME id;                      /* user name */
        TYPEKIND typeid;                /* pointer to type */
        ENTRYTYPE type;
        union{
          struct {
               int level;                /* filled in by genoffsets as it */
               int offset;               /* traverses the VARLIST */
               BOOLEAN typename;         /* need to distinguish Integer as
                                            special ID */
               BOOLEAN initialized;      /* lhs of assign or read */
               BOOLEAN used;             /* rhs or write */
               int loopopen;             /* for loops */
          }ventry;
          struct {
               int nestinglevel;
               int startlabel;
               int actrecsize;            /* 4 + space for parameters */
               SYMTABPTR localtable;      /* local vars; formal params */
               PLISTPTR plist;            /* the list of formal parars */
               BOOLEAN bodydeclared;
          }pentry;
        }aa;
} ATTREC, *ATTRPTR;
/* dynamic memory allocation of attrec node */
#define attr_alloc() (ATTRPTR)(malloc(sizeof(ATTREC)))
For completeness, let's look at the definition of PLISTPTR, as well as the typedef's that the symbol table entry will be using.
typedef short PARAMMODE; /* for parameter lists */
#define in     0
#define out    1
#define in_out 2

typedef struct plist {             /* parameter list entry */
          int level;               /* static nesting level */
          int offset;              /* in activation record */
          TYPEKIND typeid;         /* include here to make checking */
          IDNAME id;               /*   easier on proc calls */
          PARAMMODE mode;          /* in; out; in_out */
          struct attrec *loc;      /* associated var in symtab */
          struct plist *next;
          int totalsize;           /* accumulated size for all fparams */
} PLIST, *PLISTPTR;
/* dynamic storage allocation of plist */
#define plist_alloc() (PLISTPTR)(malloc(sizeof(PLIST)))

typedef struct symtabrec *SYMTABPTR;
typedef struct stmt *STMTLIST;
typedef struct varnode *VARLIST;


Semantic Stack


We need an entry on the semantic stack for each procedure. This is a "semstack.h" declaration of this stack:
struct semstackentry{
     SEMSTACKTAG type;
     union{
          ADDRTREE addr;           /* addrentry */
          EXPRTREE expr;           /* exprentry,bexprentry */
          IDNAME id;               /* identry */
          struct {
                 int iconst;
                 TYPEKIND typeid; /* differentiate (b/i) constants by typeid */
          } constant;
          OPERATION oper;          /* operentry */
          STMTPTR stmt;            /* stmtentry */
          struct {
               STMTPTR first,last;
          }slist;                  /* stlstentry */
          struct {
               PARAMPTR first,last;
          }plist;                  /* parmentry */
          char *strconst;          /* strconentry */
          TYPEKIND type;           /* typeentry */
          struct {
               IDLIST first,last;
          } ilist;                 /* idlist */
          struct {
                  PLISTPTR first,last;
          }palist;                 /* procedure parameter list */
          struct {
              PROCLIST first,last;
              ATTRPTR oldcurrentproc;    /* used to restore CurrentProc
                                            when procbody is closed */
              ATTRPTR stentry;           /* used to get to localtable */
              int endlabel;
          }prlist;
     }se;
}Semstack[STACKDEPTH];
int Semtop;
as well as the "tags"
#define plistentry    15  /* list of parameters using se.palist */
#define prlistentry   16  /* list of procedures using se.prlist */
#define subprocentry  17  /* structure for procedure declaration, this
                             uses the se.prlist fields */
You want to make sure your print_semstack can print out these 3 new types of entries. NOTE, subprocentry and prlistentry will both use the same structure of prlist.
When we are in startproc, you need to add to the top of Semstack the subprocentry with
Semstack[Semtop].se.prlist.first = proc_alloc();
where (as indicated above) we declared a PROCDEF as:
typedef struct prlist {
     IDNAME id;               /* procedure's name in characters */
     ATTRPTR thisproc;        /* pointer to proc's name in symtab */
     ATTRPTR oldcurrentproc;  /* pointer to old current procedure */
     STMTLIST procstmts;      /* pointer to stmts in body of proc */
     struct prlist *procfirst,*proclast,*next;     /* proclist */
} PROCDEF, *PROCLIST;
#define proc_alloc() (PROCLIST) (malloc(sizeof(PROCDEF)))

Simple Test File with Semantic Actions


How are these structures built and used. Let's use a simpler test program than advtest to see the timing of the semantic routines indicated in the grammar above.
--
-- This is a test of nested non-recursive parameterless procedure
-- calls with no local variables.
--
package test0 is body
    i : integer;
    procedure addone is
        procedure okay is
        begin
            i := i + 1;
        end;
    begin
        okay;
    end;
begin
    i := 0;
    writeln("i = ", i, " (should be 0)");
    addone;
    writeln("i = ", i, " (should be 1)");
    addone;
    writeln("i = ", i, " (should be 2)");
    addone;
    writeln("i = ", i, " (should be 3)");
    writeln("All done!");
end;
  1. The global symbol table will contain: i addone
  2. The level 1 symbol table will contain: okay
  3. The level 2 symbol table will be NULL.

The sequence of semantic actions is:

nullproc (for addone and any other level 1 procs)
bump_scope (working on level 1 instead of level 0 = globals)
pushid("addone");
nullparamlist

startproc (generate start label etc, set up attrec in level 0 table)

nullproc (for okay and any other level 2 procs)
bump_scope (working on level 2 instead of level 1)
pushid("okay");
nullparamlist

startproc (generate start label etc, set up attrec in level 1 table)

nullproc (which will stay NULL since there is no proc below okay)
merge_sub_procs (the subproc of okay is NULL)
nulllist (start building the statement list for okay)

          generate the code for the body of procedure okay

endprocbody (finished with proc okay, close the level 2 scope)
mergeprocs (merge the level 2 procedure into the level 2 list)
merge_sub_procs (merge the level 2 list as sub_procs of level 1)

nulllist (start the body of the level 1 procedure)

         pushid("okay");
         nullarglist (for the call to procedure okay inside addone)
         make_proc_call (for the call to procedure okay inside addone)

endprocbody (finished with proc addone, close the scope at level 1)
mergeprocs (merge the level 1 procecures into a level 1 list)

nulllist (start translating the body of the main program)

         pushid("addone");
         nullarglist (for the call to proc addone inside main)
         make_proc_call (for the call to proc addone inside main)

done (hook up the fields of Prog)

Let's look at the individual semantic actions above:
/* --------------------------------------------------------------------- */
/* --------------------------  NULLPROC -------------------------------- */
/* --------------------------------------------------------------------- */

/* procdecllist ::= lambda # nullproc                                    */
/* SEMANTIC STACK MANIPULATION:
 *                                 |prlistentry|<- top    ***** changed
 *                                 -------------
 *      top ->|     ?    |         |      ?    |
 *            ------------         -------------
*/
{add prlistentry to stack with all its fields set NULL}
/* -----------------------------------------------------------*/
/*                  BUMP SCOPE                                */
/* -----------------------------------------------------------*/

/* subproc_head ::= Procedure  #bump_scope                    */

          {set scope_array[cur_proc_level] = Root;
               cur_proc_level++;
               Root = NULL;  (to set up a new table)
          }

/* -------------------------------------------------------------------- */
/* ----- NULLPARAMLIST: Place null plist node on the semantic stack   - */
/* -------------------------------------------------------------------- */
/* SEMANTIC STACK MANIPULATION:
 *            |     ?     |   >>>   | plistentry|<- top
 *            -------------         -------------
 *       top->|  identry  |         | identry   |
 *            -------------         -------------
 *            |prlistentry|         |prlistentry|
 *                                                                      */
/*      formal_opt ::= lambda #nullparamlist                            */


          {increment Semtop and set type to plistentry
           allocate a plist_alloc();
           set fields NULL
          }

/* -------------------------------------------------------------------- */
/* ----------- STARTPROC:  Start procedure  --------------------------- */
/* -------------------------------------------------------------------- */
/* PRODUCTIONS:
 *   subprog_spec ::= subproc_head identifier formal_opt ; # startproc
 */
/* SEMANTIC STACK MANIPULATION:
 *                                  |subprocentr|  <- top
 *                                  -------------
 *      top ->| plistentry|         | plistentry|
 *            -------------         -------------
 *            |  identry  |   >>>   |  identry  |
 *            -------------         -------------
 *            |prlistentry|         |prlistentry|
 */

          {save Root in scope_array[cur_proc_level]
           Lookup "identry" in the table one level above
                               (i.e. scope_array[cur_proc_level-1])
           ---- shouldn't be there.
           allocate attr_alloc() and set id,type,nestinglevel,
                                         startlabel,actrecsize(4),
                                         localtable(NULL),first(from Semtop),
                                         bodydeclared(FALSE)
           ---- unless it's there without a body
                                     set scope_array[c_p_l] to localtable

           set scope_array[c_p_l-1] to Root; Reset Root to scope_array[c_p_l]
           so that any formal parameters will be insert in new table.

           Increment Semtop, proc_alloc() and set id field to Semtop-2.
                                              set remaining fields to NULL
          }

/* ----------------------------------------------------------------------- */
/* -------- MERGE_SUB_PROCS : MERGE SUB PROCEDURES  ---------------------- */
/* ----------------------------------------------------------------------- */
/* PRODUCTIONS:
 *  procdeclhead ::= subproc_spec Is decllist procdecllist # merge_sub_procs
 */
/* SEMANTIC STACK MANIPULATION:
 *      top ->|prlistentry|
 *            -------------
 *            |subprocent |   >>>   |subprocentry| <- top
 *            -------------         --------------
 *            |plistentry |         | plistentry |
 *            -------------         --------------
 *            | identry   |         |  identry   |
 *            -------------         --------------
 *            |prlistentry|         | prlistentry|
 */

          {decrement Semtop, set Semtop.first->procfirst to oldtop.first
                             set Semtop.first->proclast to oldtop.last
          }


/* ---------------------------------------------------------------------- */
/* ------- ENDPROCBODY:  End procedure decl  ---------------------------- */
/* ---------------------------------------------------------------------- */
/* PRODUCTIONS:
 *   procdecl ::= procdeclhead Begin stmtlist End
                                id_option2 Termsym # endprocbody          */
/* SEMANTIC STACK MANIPULATION:
 *      top ->|stlstentry |
 *            -------------
 *            |subprocent |
 *            -------------
 *            |plistentry |
 *            -------------
 *            | identry   |   >>>   |subprocentry| <- top
 *            -------------         -------------
 *            |prlistentry|         |prlistentry |
 */

          {set scope_array[c_p_l]=Root and decrement cur_proc_level
           remove prior scope symbol table (scope[c_p_l-1]) by re-setting Root
           take "identry" and Look up in prior scope table and attr_alloc,
                   should be there.  set its localtable to prior scope symtab
           decrement Semtop by 3, set type to subprocentry.
           set Semtop.first to Semtop+2.first
               Semtop.first->thisproc = attr
               Semtop.first->procstmts = Semtop+3.first
          }

/* ---------------------------------------------------------------------- */
/* ---------------- MERGEPROCS:  MERGE A PROC INTO A PROCLIST ----------- */
/* ---------------------------------------------------------------------- */
/* PRODUCTIONS:
 *   procdecllist ::= procdecllist procdecl #mergeprocs
 */

/* SEMANTIC STACK MANIPULATION:
 *      top ->|subprocentry|
 *            --------------
 *            |prlistentry |   >>>   |prlistentry|<- top
 *            --------------         -------------
 *            |      ?     |         |    ?      |
 */

          {use local variable PROCLIST newproc; for Semtop.first
           if Semtop-1.first is NULL, set Semtop-1.first=newproc
                               else   set Semtop-1.last=newproc
           decrement Semtop
          }

New Statement Structure for Proc Call

To implement the procedure call, we need a new statement structure:
typedef struct stmt{ /* statement description */
     STMTTYPE type;
     union {
          struct {
               EXPRTREE bexpr;
               struct stmt *slist;
               struct stmt *elsiflist;
               struct stmt *elselist;
          }ifthen;               /* ifthenstmt, elseif & else */
          struct {
               ATTRPTR id;
               EXPRTREE bexpr;
               struct stmt *slist;
          }loop;               /* loopstmt */
          struct {
               ATTRPTR id;
               EXPRTREE bexpr;
          }exit;               /* exitstmt */
          struct {
               ADDRTREE lhs;
               EXPRTREE rhs;
          }assign;               /* assignstmt */
          struct {
               PROCEDURETYPE type;
               PARAMLIST params;
          }procall;               /* stdproccall - reads/writes */
          struct {
                  IDNAME id;
                  int startlabel;
                  PARAMLIST paramlist;
                  PLISTPTR palist;
          }proccall;              /* procedure call */
     }se;
     struct stmt *next;     /* next statement in list */
} STMTNODE, *STMTPTR; /* stmtlistnode */
/* dynamic memory allocation of statement list node */
#define stmt_alloc() (STMTLIST)(malloc(sizeof(STMTNODE)))
/* --------------------------------------------------------------------- */
/* ------ NULLARGLIST: Place null plist node on the semantic stack   --- */
/* --------------------------------------------------------------------- */
/* SEMANTIC STACK MANIPULATION:
 *            |    ?     |   >>>   |plistentry|<- top
 *            ------------         ------------
 *       top->|    ?     |         |     ?    |
 *
 */
/*  name_suffix_list ::= lambda # nullarglist                            */

          { increment Semtop, set type to parmentry
                              set Semtop.first to NULL
          }



/* --------------------------------------------------------------------- */
/* ------ MAKEPROCCALL:  Make procedure call node ---------------------- */
/* --------------------------------------------------------------------- */
/* PRODUCTIONS:
 *   call_statement ::= name # makeproccall                              *
 *          NOTE     name ::= identifier name_suffix_list happened first *
 */
/* SEMANTIC STACK MANIPULATION:
 *
 *      top ->|plistentry|
 *            ------------         -----------
 *            | identry  |   >>>   |stmtentry| <- Semtop
 *            ------------         -----------
 *            |    ?     |         |    ?    |
 */

          {allocate a stmt_alloc, set type to proccallstmt
                                  set paramlist to Semtop.plist.first
                                  set id from Semtop-1
                                  search through the scope_array for the
                                     id - if not found then proc doesn't
                                     exist
                                  when found (at whatever scope-level,
                                     set the startlabel from attr
           decrement Semtop and set entry to statemententry
          }

Concerns for the Forward Reference

There are some additional concerns for the forward referencing of procedures. We need to build the symbol table entry for the procedure but keep track that its body has not been declared yet.
/* ---------------------------------------------------------------------- */
/* ------- ENDNULLPROCBODY:  End null procedure decl  ------------------- */
/* ---------------------------------------------------------------------- */
/* PRODUCTIONS:
 *   procdecl ::= subprog_spec Termsym # endnullprocbody
 */
/* SEMANTIC STACK MANIPULATION:
 *
 *            -------------
 *     top -> |subprocent |
 *            -------------
 *            |plistentry |
 *            -------------
 *            | identry   |   >>>   |subprocentry| <- top
 *            -------------         -------------
 *            |prlistentry|         |prlistentry|
 */
          {set scope_array[c_p_l] = Root and decrement cur_proc_level
           recover Root from scope_array[c_p_l]
           attr = Lookup "identry" from Semtop-2, should be found -
                          set localtable to scope_array[c_p_l+1]
           decrement Semtop by 2
           set type to subprocentry, set Semtop.first = Semtop+2.first
                                         Semtop.first->thisproc = NULL
                                         Semtop.first->procstmts=NULL
          })