Again working with the compiler from Assignment 0, in this step you will add the purely forward basic block optimizations.
The following algorithms are presented as rough guide- lines for the optimized versions of the generator functions given in the opcode table. These are deliberately incom- plete and imperfect . . . you must decide where and how to do what is missing or ambiguous.
tuple *
binop(o, t0, t1)
opcode o;
tuple *t0, *t1;
/* simplify later matching by normalizing operand order */
if (o is commutative) {
normalize order of t0 and t1;
}
/* local special-case optimizations */
if (local optimization applies) {
perform local optimization;
return(ptr. to result tuple);
}
/* constant folding */
if (both args are constants) {
fold the expression into a single constant value;
return( cop(the resulting value) );
}
/* common subexpression elimination */
if (equivalent tuple is available) {
return(ptr. to available tuple);
}
/* some things just can't be optimized away... yet */
make a new tuple for o(t0, t1);
mark tuple as non-constant-valued;
assign the tuple a new value number;
return(ptr. to this tuple);
tuple *
cop(c)
int c;
/* constant pooling (common subexpression elimination) */
if (equivalent tuple is available) {
return(ptr. to available tuple);
}
/* some things just can't be optimized away... yet */
make a new tuple for const(c);
mark tuple as non-constant-valued;
assign the tuple a new value number;
return(ptr. to this tuple);
tuple *
ldop(v)
var *v;
/* value propagation */
if (current value of var[0] is an available tuple) {
return(ptr. to available tuple);
}
/* some things just can't be optimized away... yet */
update symbol table entry for v[0];
make a new tuple for ld(v);
mark tuple as non-constant-valued;
assign the tuple a new value number;
return(ptr. to this tuple);
tuple *
ldxop(v, t)
var *v;
tuple *t;
/* value propagation */
if (current value of var[t] is an available tuple) {
return(ptr. to available tuple);
}
/* some things just can't be optimized away... yet */
update symbol table entry for v[t];
make a new tuple for ldx(v, t);
mark tuple as non-constant-valued;
assign the tuple a new value number;
return(ptr. to this tuple);
tuple *
stop(v, t)
var *v;
tuple *t;
/* symbol table stuff... */
delete all v[t0] entries where t0 is not constant;
update symbol table entry for v[0];
/* some things just can't be optimized away... yet */
update symbol table entry for v:
if (there exist entries for v[t] where t is not constant) {
delete entry for v[t];
}
make a new tuple for st(v, t);
mark tuple as non-constant-valued;
assign the tuple a new value number;
return(tuple ptr. t);
tuple *
stxop(v, t1, t2)
var *v;
tuple *t1, *t2;
/* symbol table stuff... */
if (t1 is a constant) {
delete all v[t] entries where t is not constant;
} else {
delete all v[t] entries;
}
update symbol table entry for v[t1];
/* some things just can't be optimized away... yet */
make a new tuple for stx(v, t1, t2);
mark tuple as non-constant-valued;
assign the tuple a new value number;
return(tuple ptr. t2);
tuple * labop(l) int l; /* some things just can't be optimized away... yet */ make a new tuple for label(l); update label table entry for l; return(ptr. to this tuple);
tuple *
selop(t, l1, l2)
tuple *t;
int l1, l2;
/* local special-case optimizations */
if (sel tests a constant-valued tuple) {
make t = ;
replicate the appropriate label;
}
/* some things just can't be optimized away... yet */
make a new tuple for sel(t, l1, l2);
return(ptr. to this tuple);
tuple * killop(v) var *v; /* not really an operation for forward flow.... */ update symbol table entry for v to say v is dead; make a new tuple for kill(v); return(ptr. to this tuple);
The basic tuple structure is:
#define tuple struct _tuple
tuple {
tuple *next, *prev; /* sequential order links */
opcode oarg; /* tuple opcode */
tuple *targ[2]; /* tuple arguments */
konst carg; /* constant argument */
label larg[2]; /* label arguments */
var *varg; /* variable name argument */
};
where the use of fields is obvious from the tuple descrip- tions given earlier.
All the tuple code generated will be linked into a cir- cular linked list whose permanent header node is code. Upon end of input, the tuples linked into the code list are auto- matically printed.
In the above, there are several references to the han- dling of symbol table entries. Symbol table entries are described by var * values which directly access them. A partial definition of the var structure is:
#define var struct _var
var {
char *text; /* pointer to string */
int type; /* token type of variable (e.g., KIF) */
int dim; /* size of dimension (1 means scalar) */
...
};
It will be necessary for you to add fields to this structure so that your tracking of value numbers (i.e., tuple point- ers) for particular subscripts can be accommodated. Notice that dim will be 1 for a scalar -- i.e., int a[1]; and int a; make identical var entries.
Scoping is handled automatically and, in anticipation of the global optimizer, var entries are never deleted. You may assume that the var * values passed to your routines are always valid.
TBA.