Vous êtes sur la page 1sur 4

CS143 Handout 34

Autumn 2007 November 30, 2007


Section Handout: Stack-Allocated Arrays

Problem 1: Stack Allocation of Decaf Arrays


Like all of us, gcc developers love to add extensions. For instance, gcc supports stack-
allocated arrays with non-constant sizes, as shown in this example:
// GNU gcc extension
void function(int n)
{
int array[2*n+1];
// code that uses array
}

This problem asks you to add this extension to your Decaf compiler. You must support
one-dimensional arrays whose element types can be any legal Decaf type, except stack-
allocated arrays themselves. That is, it is legal to declare

int[][n] legitimateArray;,

but it would not be legal to declare

int [n][n] illegitimateArray;.

(Remember that Decaf uses the notation int[] a, unlike C’s int a[].)

The storage for the array elements should be allocated on the stack. In this way, the
storage is automatically freed when the function returns. Array bounds checking should
be performed as for ordinary, heap-allocated arrays.

a) What changes, if any, do you have to make to the scanner?


b) What changes, if any, do you have to make to the Decaf grammar?
c) Would the changes to the grammar, once implemented in your parser.y file,
introduce new conflicts (shift/reduce or reduce/reduce)?
d) Suppose you have made the changes to your parser, if any were necessary, and
resolved all conflicts that may have occurred. What changes do you have to make
to the semantic analyzer? For this part, assume that stack-allocated arrays are type-
equivalent with their ordinary, heap-allocated counterparts if their element types
are equivalent. Assume that the identifiers declared in other declarations within
the declaration section are not visible. For example, declaring

void f() { int m; int[m] a; }


2

is illegal.

e) This extension makes Decaf less type safe. Explain why.


f) How could Decaf’s type safety be restored? Explain what restrictions would be
required to restore type safety. Describe the additional checks your semantic
analyzer would have to perform.

As in the project, we’ll provide the necessary TAC instructions for your intermediate code
generator. We define a new TAC instruction AllocA that can be emitted to allocate space
on the stack for a stack-allocated array:

class AllocA: public Instruction {


Location *szLoc; // size in bytes including header
Location *dstLoc; // location where result will be stored
public:
AllocA(Location *szLoc, Location *dstLoc)
: szLoc(szLoc), dstLoc(dstLoc) {
Assert(szLoc != NULL && dstLoc != NULL);
sprintf(printed, "%s = AllocA %s",
dstLoc->GetName(), szLoc->GetName());
}

void EmitSpecific(CodeGenerator* cg);


};

void AllocA::EmitSpecific(Mips* cg) {


cg->EmitAllocA(szLoc, dstLoc);
}

In order for this to work, you’d need to also implement one more method in the MIPS

void Mips::EmitAllocA(Location *szLoc, Location *dstLoc);

g) Given the MIPS’s procedure calling convention, explain where in the stack frame
you would allocate space for the elements of stack-allocated arrays.
h) Assuming this new stack frame layout, will this extension affect how you assign
offsets to the locations of your local variables and temporaries?
i) Implement Mips::EmitAllocA. Assume that the szLoc argument refers to a location
in which a value of 4*n + 4 is stored, where n > 0 is the number of elements in the
array. In other words, the required size of the array has already been computed
and checked. Assume further that AllocA shall only allocate the memory for the
new array, it shall not set up the array header. In short, AllocA replaces the LCall
_Alloc sequence found in the TAC sequence emitted to allocate ordinary, heap-
based arrays.
3

Solution 1: Stack Allocation of Decaf Arrays


a) No changes are required to the scanner, because the set of tokens doesn’t change.
b) The Decaf grammar needs to be changed to add a rule

Type ::= Type [ Expr ]

c) This change will introduce new conflicts, because the grammar will have to
distinguish between a variable declaration A[n] b; and a possible first statement
starting with A[n]… in the following block of statements. Your parser would have
explore both options in parallel without reducing a nonterminal that only applies to
one or the other option. This is practically impossible the way your grammar is
written. Hence, the change will result in a reduce-reduce conflict for T_Identifier
using rules such as LValue T_Identifier and Type T_Identifier. In fact, the conflict
cannot be easily resolved, because it would require you to look ahead until after the
closing ], which can be an arbitrary number of tokens. This problem could only be
fixed by using the same set of nonterminals for variable declarations and
expressions (which would be a nightmare to typecheck). As an aside, note that that
is also the reason we have the lexer synthesize a T_Dims ([]) token distinct from [
and ].

d) The necessary changes to the semantic analyzer include:

 Checking that this new type declaration is only used within a function scope
and not for globals, instance variables or formal parameters (can be done
syntactically during the parsing phase.)
 Checking that the element type of any array type declaration is not a stack-
allocated array (can also be done during parsing.)
 Checking that the expression within the brackets can be evaluated to an integer,
without however using variables in the scope in which they are defined, which
may require changes to your scoping mechanism as it’s different from the
default scoping rules in Decaf.
 Adapting the type compatibility/equivalence functions to make sure stack-
allocated arrays can be used in any place heap-allocated arrays can be used,
except possibly as the target of a NewArray or a reassignment (depending on
your assumptions.)

e) This extension makes Decaf less typesafe, because we can now end up with
dangling pointers to objects allocated on the stack if references to such stack-
allocated arrays escape the function.

f) Decaf’s type safety could be restored by not allowing

 return statements with stack-allocated arrays


 assignments where the right-hand side is a reference to a stack-allocated array
 references to stack-allocated arrays to be passed to functions or methods
4

g) Unless you want to compute the frame-pointer-relative offsets for some variables at
runtime, we need to place memory for the array elements below all of the space set
aside for local variables and temporaries. The frame can be extended at runtime
by the code emitted from Mips::AllocA, after the size expression has been allocated.
h) They won’t affect them at all, provided you plant the memory for stack arrays
below everything else.
i)

Mips::EmitAllocA(Location *szLoc, Location *dstLoc)


{
Register sizeRegister = GetRegister(szLoc);
Register destRegister = GetRegisterForWrite(dstLoc);
Emit("sub $sp, $sp, %s", regs[sizeRegister].name);
Emit("addiu %s, $sp, 4", regs[destRegister].name);
// optionally call SpillRegister(destRegister);
} // note that frame point is unaffected, so access to all named locals
// is unaffected.