CS143 Autumn 2007

Handout 34

Section Handout: Stack-Allocated Arrays

November 30, 2007

Problem 1: Stack Allocation of Decaf Arrays Like all of us, gcc developers love to add extensions. For instance, gcc supports stackallocated 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 stackallocated 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 typeequivalent 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, heapbased 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 stackallocated 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 stackallocated 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 stackallocated 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.

Master your semester with Scribd & The New York Times

Special offer for students: Only $4.99/month.

Master your semester with Scribd & The New York Times

Cancel anytime.