• 沒有找到結果。

STACK FRAMES

在文檔中 Modern Compiler Implementation in C (頁 138-142)

Activation Records

6.1 STACK FRAMES

local variables and postpone discussion of higher-order functions to Chap-ter 15.

6.1 STACK FRAMES

The simplest notion of a stack is a data structure that supports two opera-tions, push and pop. However, it turns out that local variables are pushed in large batches (on entry to functions) and popped in large batches (on exit).

Furthermore, when local variables are created they are not always initialized right away. Finally, after many variables have been pushed, we want to con-tinue accessing variables deep within the stack. So the abstract push and pop model is just not suitable.

Instead, we treat the stack as a big array, with a special register – the stack pointer– that points at some location. All locations beyond the stack pointer are considered to be garbage, and all locations before the stack pointer are considered to be allocated. The stack usually grows only at the entry to a function, by an increment large enough to hold all the local variables for that function, and, just before the exit from the function, shrinks by the same amount. The area on the stack devoted to the local variables, parameters, re-turn address, and other temporaries for a function is called the function’s ac-tivation recordor stack frame. For historical reasons, run-time stacks usually start at a high memory address and grow toward smaller addresses. This can be rather confusing: stacks grow downward and shrink upward, like icicles.

The design of a frame layout takes into account the particular features of an instruction set architecture and the programming language being compiled.

However, the manufacturer of a computer often prescribes a “standard” frame layout to be used on that architecture, where possible, by all compilers for all programming languages. Sometimes this layout is not the most convenient one for a particular programming language or compiler. But by using the

“standard” layout, we gain the considerable benefit that functions written in one language can call functions written in another language.

Figure 6.2shows a typical stack frame layout. The frame has a set of in-coming arguments(technically these are part of the previous frame but they are at a known offset from the frame pointer) passed by the caller. The re-turn addressis created by the CALL instruction and tells where (within the calling function) control should return upon completion of the current func-tion. Some local variables are in this frame; other local variables are kept in

↑higher addresses argument n

.

incoming . previous

arguments . frame

argument 2 argument 1 frame pointer → static link

local variables return address

temporaries

current

saved frame

registers argument m

.

outgoing .

arguments .

argument 2 argument 1 stack pointer → static link

next frame

↓lower addresses FIGURE 6.2. A stack frame.

6.1. STACK FRAMES

machine registers. Sometimes a local variable kept in a register needs to be savedinto the frame to make room for other uses of the register; there is an area in the frame for this purpose. Finally, when the current function calls other functions, it can use the outgoing argument space to pass parameters.

THE FRAME POINTER

Suppose a function g(. . .) calls the function f (a1, . . . ,an). We say g is the callerand f is the callee. On entry to f , the stack pointer points to the first argument that g passes to f . On entry, f allocates a frame by simply sub-tracting the frame size from the stack pointerSP.

The oldSPbecomes the current frame pointer FP. In some frame layouts,

FPis a separate register; the old value ofFPis saved in memory (in the frame) and the new FPbecomes the oldSP. When f exits, it just copiesFP back to

SPand fetches back the savedFP. This arrangement is useful if f ’s frame size can vary, or if frames are not always contiguous on the stack. But if the frame size is fixed, then for each function f theFPwill always differ fromSPby a known constant, and it is not necessary to use a register forFPat all –FPis a

“fictional” register whose value is alwaysSP+framesize.

Why talk about a frame pointer at all? Why not just refer to all variables, parameters, etc. by their offset from SP, if the frame size is constant? The frame size is not known until quite late in the compilation process, when the number of memory-resident temporaries and saved registers is determined.

But it is useful to know the offsets of formal parameters and local variables much earlier. So, for convenience, we still talk about a frame pointer. And we put the formals and locals right near the frame pointer at offsets that are known early; the temporaries and saved registers go farther away, at offsets that are known later.

REGISTERS

A modern machine has a large set of registers (typically 32 of them). To make compiled programs run fast, it’s useful to keep local variables, intermediate results of expressions, and other values in registers instead of in the stack frame. Registers can be directly accessed by arithmetic instructions; on most machines, accessing memory requires separate load and store instructions.

Even on machines whose arithmetic instructions can access memory, it is faster to access registers.

A machine (usually) has only one set of registers, but many different pro-cedures and functions need to use registers. Suppose a function f is using

register r to hold a local variable and calls procedure g, which also uses r for its own calculations. Then r must be saved (stored into a stack frame) before guses it and restored (fetched back from the frame) after g is finished using it. But is it f ’s responsibility to save and restore the register, or g’s? We say that r is a caller-save register if the caller (in this case, f ) must save and re-store the register, and r is callee-save if it is the responsibility of the callee (in this case, g).

On most machine architectures, the notion of caller-save or callee-save reg-ister is not something built into the hardware, but is a convention described in the machine’s reference manual. On the MIPS computer, for example, reg-isters 16–23 are preserved across procedure calls (callee-save), and all other registers are not preserved across procedure calls (caller-save).

Sometimes the saves and restores are unnecessary. If f knows that the value of some variable x will not be needed after the call, it may put x in a caller-save register and not save it when calling g. Conversely, if f has a local variable i that is needed before and after several function calls, it may put i in some callee-save register ri and, save ri just once (upon entry to f ) and fetch it back just once (before returning from f ). Thus, the wise selection of a caller-save or callee-save register for each local variable and temporary can reduce the number of stores and fetches a program executes. We will rely on our register allocator to choose the appropriate kind of register for each local variable and temporary value.

PARAMETER PASSING

On most machines whose calling conventions were designed in the 1970s, function arguments were passed on the stack.1But this causes needless mem-ory traffic. Studies of actual programs have shown that very few functions have more than four arguments, and almost none have more than six. There-fore, parameter-passing conventions for modern machines specify that the first k arguments (for k = 4 or k = 6, typically) of a function are passed in registers rp, ...,rp+k−1, and the rest of the arguments are passed in memory.

Now, suppose f (a1, . . . ,an)(which received its parameters in r1, . . . ,rn, for example) calls h(z). It must pass the argument z in r1; so f saves the old contents of r1(the value a1) in its stack frame before calling h. But there is the memory traffic that was supposedly avoided by passing arguments in registers! How has the use of registers saved any time?

1Before about 1960, they were passed not on the stack but in statically allocated blocks of memory, which precluded the use of recursive functions.

6.1. STACK FRAMES

There are four answers, any or all of which can be used at the same time:

1. Some procedures don’t call other procedures – these are called leaf proce-dures. What proportion of procedures are leaves? Well, if we make the (opti-mistic) assumption that the average procedure calls either no other procedures or calls at least two others, then we can describe a “tree” of procedure calls in which there are more leaves than internal nodes. This means that most proce-dures called are leaf proceproce-dures.

Leaf procedures need not write their incoming arguments to memory. In fact, often they don’t need to allocate a stack frame at all. This is an important savings.

2. Some optimizing compilers use interprocedural register allocation, analyz-ing all the functions in an entire program at once. Then they assign different procedures different registers in which to receive parameters and hold local variables. Thus f (x) might receive x in r1, but call h(z) with z in r7.

3. Even if f is not a leaf procedure, it might be finished with all its use of the

在文檔中 Modern Compiler Implementation in C (頁 138-142)