• No results found

Name Spaces

In document Engineering A Compiler pdf (Page 181-188)

Global Name Space – Procedure Names – Common Blocks

– qualified variable names Procedure Name Space

– Variables & Parameters

C

Global Name Space – Procedures – Global Variables File Static Storage

– Procedures – Variables

Procedure Name Space – Variables & Parameters Block Name Space

– Variables

PL/I

Global Name Space – Variables – Procedures Procedure Name Space

– Variables & Parameters – Procedures

Scheme

Global Name Space – Objects

– Built-in objects Local Name Space

– Objects

Figure 7.3: Name Space Structure of Different Languages

Pascal andpl/ihave simple Algol-like scoping rules. As with Fortran, they have a global name space that holds procedures and variables. As with Fortran, each procedure creates its own local name space. Unlike Fortran, a procedure can contain the declarations of other procedures, with their own name spaces. This creates a set of nested lexical scopes. The example in Figure 7.1 does this, nesting FeeandFieinMain, andFuminsideFoeinsideFie. Section 7.3.5 examines nested scopes in more detail.

7.3.2 Activation Records

The creation of a new, separate, name space is a critical part of the procedure abstraction. Inside a procedure, the programmer can declare named variables that are not accessible outside the procedure. These named variables may be initialized to known values. In Algol-like languages, local variables have lifetimes that match the procedures that declare them. Thus, they require storage during the lifetime of the invocation and their values are of interest only while the invocation that created them is active.

To accommodate this behavior, the compiler arranges to set aside a region of memory, called anactivation record(ar), for each individual call to a procedure. Thear is allocated when the procedure is invoked; under most circumstances, it is freed when control returns from the procedure back to the point in the

locals caller arp access link return address return value register save area parameters arp - -

Figure 7.4: A typical activation record

program that called it. The ar includes all the storage required for the pro- cedure’s local variables and any other data needed to maintain the illusion of the procedure. Unless a separate hardware mechanism supports call and return, this state information includes the return address for this invocation of the pro- cedure. Conveniently, this state information has the same lifetime as the local variables.

Whenpcallsq, the code sequence that implements the call must both pre- servep’s environment and create a new environment forq. All of the information required to accomplish this is stored in their respective ars. Figure 7.4 shows how the contents of anarmight be laid out. It contains storage space for local variables, thearpointer of the calling procedure, a pointer to provide access to variables in surrounding lexical scopes, a slot for the return address in the call- ing procedure, a slot for storing the returned value (or a pointer to it), an area for preserving register values on a call, and an area to hold parameter values. The entire record is addressed through an activation record pointer, denoted arp. Taken together, the return address and the caller’s arp form a control linkthat allows the code to return control to the appropriate point in the calling procedure.

7.3.3 Local Storage

The activation record must hold all of the data and state information required for each invocation of a procedure p. A typicalarmight be laid out as shown in Figure 7.4. The procedure accesses itarthrough thearp. (By convention, thearp usually resides in a fixed register. In theiloc examples,r0 holds the arp.) Thearp points to a designated location in thearso that all accesses to thearcan be made relative to thearp.

Items that need space in thearinclude thearp of the calling procedure, a return address, any registers saved in calls that the current procedure will make, and any parameters that will be passed to those procedures. Thearmay also include space for a return value, if the procedure is a function, and anaccess link that can be used to find local variables of other active procedures.

7.3. NAME SPACES 173 Space for Local Data Each local data item may need space in the ar. The compiler should assign each location an appropriately-sized area, and record in the symbol table its offset from thearp. Local variables can then be accessed as offsets from the arp. Iniloc, this is accomplished with aloadAOinstruction. The compiler may need to leave space among the local variables for pointers to variable-sized data, such as an array whose size is not known at compile time. Space for Variable-length Data Sometimes, the compiler may not know the size of a data item at compile time. Perhaps its size is read from external media, or it must grow in response to the amount of data presented by other procedures. To accommodate such variable-sized data, the compiler leaves space for a pointer in thear, and then allocates space elsewhere (either in the heap or on the end of the current ar) for the data once its size is known. If the register allocator is unable to keep the pointer in a register, this introduces an extra level of indirection into any memory reference for the variable-length data item. If the compiler allocates space for variable-length data in the ar, it may need to reserve an extra slot in thearto hold a pointer to the end of the ar.

Initializing Variables A final part of ar maintenance involves initializing data. Many languages allow the programmer to specify a first, or initial, value for a variable. If the variable is allocated statically—that is, it has a lifetime that is independent of any procedure—the data can be inserted directly into the appropriate locations by the linker/loader. On the other hand, local variables must be initialized by executing instructions at run-time. Since a procedure may be invoked multiple times, and itsars may be placed at different addresses, the only feasible way to set initial values is to generate instructions that store the necessary values to the appropriate locations. This code must run before the procedure’s first executable statement.

Space for Saved Register Values When p callsq, either p or q must save the register values that pneeds. This may include all the register values; it may be a subset of them. Whichever procedure saves these values must restore them after q’s body finishes executing. This requires space, in the ar, for the saved registers. If the caller saves its own registers, then p’s register save area will contain values related to its own execution. If the callee saves the caller’s registers, then the value of p’s registers will be preserved in q’s register save area.

7.3.4 Allocating Activation Records

The compiler must arrange for an appropriately sized activation record for each invocation of a procedure. The activation record must be allocated space in memory. The space must be available when the calling procedure begins to execute the procedure call, so that it can fill in the various slots in thearthat contain information not known at compile-time, establishing both the control link and the access link. (Since a procedure can be called from many call sites, this information cannot, in general, be known before the invocation.) In general,

space for A pointer toA locals caller arp

· · ·

arp - - - old tos - new tos

Figure 7.5: Allocating a dynamically sized array

the compiler has several choices for how to allocate activation records.

Stack Allocation of Activation Records When the contents of anarare limited to the lifetime of the procedure invocation that caused its creation, and a called procedure cannot outlive its caller, the compiler can allocatears using a stack discipline. With these restrictions, calls and returns are balanced; each called procedure eventually returns, and any returns occurring between a procedurep’s invocation andp’s eventual return are the result (either directly or indirectly) of some procedure call made insidep. Stack allocation is attractive because the cost of both allocation and deallocation are small—a single arithmetic operation. (In contrast, general heap management schemes require much more work. See Section 7.7.)

Local variables whose sizes are not known at compile time can be handled within a stack allocation scheme. The compiler must arrange to allocate them at the end of the arthat matches the end of the stack. It must also keep an explicit pointer to the end of the stack. With these provisions, the running code can extend thear and the stack to create space when the variable’s size becomes known. To simplify addressing, the compiler may need to set aside a slot in the local variable area that holds a pointer to the actual data. Figure 7.5 shows the lower portion of an activation record where a dynamically-sized array,

A, has been allocated at the end of thear. The top of stack position (tos) is shown both before and after the allocation ofA.

With stack-allocatedars, the ar for the currently executing procedure al- ways resides at the top of the stack. Unless the size of Aexceeds the available stack space, it can be added to the current ar. This allocation is inexpensive; the code can simply store tos in the slot reserved for a pointer to A and in- crementtosby the size of A. (Compare this to the cost of heap allocation and deallocation in Section 7.7.) Of course, when it generates code for this alloca- tion, the compiler can insert a test that checks the size of A against available

7.3. NAME SPACES 175 space. If insufficient stack space is available, it can either report an error or try another allocation mechanism. To allocateAon the heap would require the same pointer field for A in the ar. The code would simply use the standard heap management routines to allocate and free the space.

Heap Allocation of Activation Records If a procedure can outlive its caller, as in continuation-passing style, stack allocation is inappropriate because the caller’s activation record cannot be freed. If a procedure can return an object that includes, explicitly or implicitly, references to its local variables, stack alloca- tion is inappropriate because it will leave behind dangling pointers. In these situations,ars can be kept in heap storage. This lets the code dismantle and free anar when all the pointers to it have been discarded. Garbage collection is the natural technology for reclaiming ars in such an environment, since the collector will track down all the pointers and ensure safety. (See Section 7.7.) Static Allocation of Activation Records If a procedure q calls no other proce- dures, then q can never have more than a single active invocation. We call

q aleaf procedure since it terminates a path through a graph of the possible procedure calls. The compiler can statically allocate activation records for leaf procedures. If the convention requires a caller to save its own registers, q will not need the corresponding space in itsar. This saves the run-time cost of ar allocation.

At any point in the execution of the compiled code, only one leaf procedure can be active. (To have two such procedures active, the first one would need to call another procedure, so it would not be a leaf.) Thus, the compiler can allocate a single static ar for use by all of the leaf procedures. The static ar must be large enough to accommodate any leaf procedure. The static variables declared in any of the leaf procedures can be laid out together in that singlear. Using a single staticarfor leaf procedures reduces the run-time space overhead of staticarallocation.

Coalescing Activation Records If the compiler discovers a set of procedures that are always invoked in a fixed sequence, it may be able to combine their activation records. For example, if a call from fee to fiealways results in calls to foe

and fum, the compiler may find it profitable to allocate the ars forfie, foe, and fum at the same time. If ars are allocated on the heap, the savings are obvious. For stack-allocatedars, the savings are minor.

If all the calls tofiecannot be changed to allocate the coalesced ar, then the calls tofoeandfumbecome more complex. The calling sequence generated must recognize when to allocate a new ar and when one already exists. The compiler must either insert conditional logic in the calling sequence to handle this, or it can generate two copies of the code for the affected procedures and call the appropriate routines. (The latter is a simple case of a transformation known asprocedure cloning. See Chapter 14.)

program Main(input, output); var x,y,z: integer; procedure Fee; var x: integer; begin { Fee } x := 1; y := x * 2 + 1 end; procedure Fie; var y: real; procedure Foe; var z: real; procedure Fum; var y: real; begin { Fum } x := 1.25 * z; Fee; writeln(’x = ’,x) end; begin { Foe } z := 1; Fee; Fum end; begin { Fie } Foe; writeln(’x = ’,x) end; begin { Main } x := 0; Fie end. Name Resolution Scope x y z

Main Main.x Main.y Main.z

Fee Fee.x Main.y Main.z

Fie Main.x Fie.y Main.z

Foe Main.x Fie.y Foe.z

Fum Main.x Fum.y Foe.z

Nesting Relationships Main n Fee n Fie n Foe n Fum , , @ @ R ? ? Calling Relationships Main n Fee n Fie n Foe n Fum @ @ R ? H H H H Y ? @ @ @ @ I

Figure 7.6: Nested lexical scopes in Pascal

7.3.5 Nested lexical scopes

Many Algol-like languages allow the programmer to define new name spaces, or lexical scopes, inside other scopes. The most common case is nesting procedure definitions inside one another; this approach is exemplified by Pascal. Any Pascal program that uses more than one procedure has nested scopes. C treats each new block, denoted by brackets { and }, as a distinct scope. Thus, the programmer can declare new variables inside each block.

Lexical scoping rules follow one general principle. Inside a given scope, names are bound to the lexically closest declaration for that name. Consider, again, our example Pascal program. It is shown, along with information about the nesting of lexical scopes, in Figure 7.6. It contains five distinct scopes, one corresponding to the programMainand one for each of the proceduresFee,Fie,

7.3. NAME SPACES 177

Foe, andFum. Each procedure declares some set of variables drawn from the set of namesx, y, and z. Pascal’s name scoping rules dictate that a reference to a variable is bound to the nearest declaration of that name, in lexical order. Thus, the statementx := 1;insideFeerefers to the integer variablexdeclared in Fee. Since Feedoes not declarey, the next statement refers to the integer variable y declared in Main. In Pascal, a statement can only “see” variables declared in its procedure, or in some procedure that contains its procedure. Thus, the assignment toxinFumrefers to the integer variable declared inMain, which contains Fum. Similarly, the reference toz in the same statement refers to the variable declared inFoe. WhenFumcallsFee,Fumdoes not have access to the value of x computed by Fee. These scoping relationships are detailed in the table on the right-hand side of Figure 7.6. The nesting relationships are depicted graphically immediately below the table. The bottom graph shows the sequence of procedure calls that occur as the program executes.

Most Algol-like languages implement multiple scopes. Some, like Pascal, pl/i, and Scheme, allow arbitrary nesting of lexical scopes. Others have a small, constant set of scopes. Fortran, for example, implements a global name space and a separate name space for each subroutine or function. (Features such as block data, statement functions, and multiple-entry procedures slightly complicate this picture.)

C implements nested scopes for blocks, but not for procedures. It uses the static scope, which occurs at the file-level, to create a modicum of the modularity and information hiding capabilities allowed by Pascal-style nested procedures. Since blocks lack both parameter binding and the control abstrac- tion of procedures, the actual use of nested scopes is far more limited inc. One common use for the block-level scope is to create local temporary storage for code generated by expanding a pre-processor macro.

In general, each scope corresponds to a different region in memory, sometimes called adata area. Thus, the data area in a procedure’s activation record holds its locally-declared variables. Global variables are stored in a data-area that can be addressed by all procedures.

To handle references in languages that use nested lexical scopes, the compiler translates each name that refers to a local variable of some procedure into a pair level,offset, where level is the lexical nesting level of the scope that declared the variable andoffset is its memory location relative toarp. This pair is the variable’s static distance coordinate. The translation is typically done during parsing, using a lexically-scoped symbol table, described in Section 6.7.3. The static distance coordinate encodes all the information needed by the code gen- erator to emit code that locates the value at run time. The code generator must establish a mechanism for finding the appropriateargivenlevel. It then emits code to load the value found at offset inside thatar. Section 7.5.2 describes two different run-time data structures that can accomplish this task.

In document Engineering A Compiler pdf (Page 181-188)