K. Cooper, L. Torczon - Engineering a Compiler (2011 - 2nd edition) (798440), страница 98
Текст из файла (страница 98)
We assume thatmost procedures are called from several locations; if not, both the programmer and the compiler should consider including the procedure inline at thepoint of its only invocation.From the code-shape perspective, procedure calls are similar in Algol-likelanguages and object-oriented languages. The major difference betweenthem lies in the technique used to name the callee (see Section 6.3.4). Inaddition, a call in an object-oriented language typically adds an implicitactual parameter, that is, the receiver’s object record.7.9.1 Evaluating Actual ParametersWhen it builds the precall sequence, the compiler must emit code to evaluatethe actual parameters to the call.
The compiler treats each actual parameteras an expression. For a call-by-value parameter, the precall sequence evaluates the expression and stores its value in a location designated for thatparameter—either in a register or in the callee’s ar. For a call-by-referenceparameter, the precall sequence evaluates the parameter to an address andstores the address in a location designated for that parameter. If a call-byreference parameter has no storage location, then the compiler may need toallocate space to hold the parameter’s value so that it has an address to passto the callee.If the source language specifies an order of evaluation for the actual parameters, the compiler must, of course, follow that order.
Otherwise, it shoulduse a consistent order—either left to right or right to left. The evaluationorder matters for parameters that might have side effects. For example, aprogram that used two routines push and pop to manipulate a stack wouldproduce different results for the sequence subtract(pop(), pop()) underleft-to-right and right-to-left evaluation.Procedures typically have several implicit arguments. These include theprocedure’s arp, the caller’s arp, the return address, and any information394 CHAPTER 7 Code Shapeneeded to establish addressability. Object-oriented languages pass thereceiver as an implicit parameter. Some of these arguments are passed inregisters while others usually reside in memory. Many architectures have anoperation likejsr label1 ⇒ rithat transfers control to label1 and places the address of the operation thatfollows the jsr into ri .Procedures passed as actual parameters may require special treatment.
If pcalls q, passing procedure r as an argument, p must pass to q more information than r’s starting address. In particular, if the compiled code uses accesslinks to find nonlocal variables, the callee needs r’s lexical level so that asubsequent call to r can find the correct access link for r’s level. The compiler can construct an haddress,leveli pair and pass it (or its address) in placeof the procedure-valued parameter.
When the compiler constructs the precallsequence for a procedure-valued parameter, it must insert the extra code tofetch the lexical level and adjust the access link accordingly.7.9.2 Saving and Restoring RegistersUnder any calling convention, one or both of the caller and the callee mustpreserve register values. Often, linkage conventions use a combination ofcaller-saves and callee-saves registers. As both the cost of memory operations and the number of registers have risen, the cost of saving and restoringregisters at call sites has increased, to the point where it merits carefulattention.In choosing a strategy to save and restore registers, the compiler writer mustconsider both efficiency and code size. Some processor features impact thischoice. Features that spill a portion of the register set can reduce code size.Examples of such features include register windows on the sparc machines,the multiword load and store operations on the Power architectures, and thehigh-level call operation on the vax.
Each offers the compiler a compactway to save and restore some portion of the register set.While larger register sets can increase the number of registers that the codesaves and restores, in general, using these additional registers improves thespeed of the resulting code. With fewer registers, the compiler would beforced to generate loads and stores throughout the code; with more registers,7.9 Procedure Calls 395many of these spills occur only at a call site. (The larger register set shouldreduce the total number of spills in the code.) The concentration of savesand restores at call sites presents the compiler with opportunities to handle them in better ways than it might if they were spread across an entireprocedure.nnnUsing multi-register memory operations When saving and restoringadjacent registers, the compiler can use a multiregister memoryoperation.
Many isas support doubleword and quadword load and storeoperations. Using these operations can reduce code size; it may alsoimprove execution speed. Generalized multiregister memory operationscan have the same effect.Using a library routine As the number of registers grows, the precalland postreturn sequences both grow. The compiler writer can replacethe sequence of individual memory operations with a call to acompiler-supplied save or restore routine.
Done across all calls, thisstrategy can produce a significant savings in code size. Since the saveand restore routines are known only to the compiler, they can useminimal call sequence to keep the runtime cost low.The save and restore routines can take an argument that specifies whichregisters must be preserved. It may be worthwhile to generate optimizedversions for common cases, such as preserving all the caller-saves orcallee-saves registers.Combining responsibilities To further reduce overhead, the compilermight combine the work for caller-saves and callee-saves registers.
Inthis scheme, the caller passes a value to the callee that specifies whichregisters it must save. The callee adds the registers it must save to thevalue and calls the appropriate compiler-provided save routine. Theepilogue passes the same value to the restore routine so that it canreload the needed registers. This approach limits the overhead toone call to save registers and one to restore them.
It separatesresponsibility (caller saves versus callee saves) from the cost tocall the routine.The compiler writer must pay close attention to the implications of the various options on code size and runtime speed. The code should use the fastestoperations for saves and restores. This requires a close look at the costs ofsingle-register and multiregister operations on the target architecture.
Usinglibrary routines to perform saves and restores can save space; careful implementation of those library routines may mitigate the added cost of invokingthem.396 CHAPTER 7 Code ShapeSECTION REVIEWThe code generated for procedure calls is split between the callerand the callee, and between the four pieces of the linkage sequence(prologue, epilogue, precall, and postreturn). The compiler coordinatesthe code in these multiple locations to implement the linkage convention, as discussed in Chapter 6.
Language rules and parameter bindingconventions dictate the order of evaluation and the style of evaluationfor actual parameters. System-wide conventions determine responsibilityfor saving and restoring registers.Compiler writers pay particular attention to the implementation ofprocedure calls because the opportunities are difficult for generaloptimization techniques (see Chapters 8 and 10) to discover. Themany-to-one nature of the caller-callee relationship complicates analysisand transformation, as does the distributed nature of the cooperatingcode sequences. Equally important, minor deviations from the definedlinkage convention can cause incompatibilities in code compiled withdifferent compilers.Review Questions1.
When a procedure saves registers, either callee-saves registers in itsprologue or caller-saves registers in a precall sequence, where shouldit save those registers? Are all of the registers saved for some callstored in the same AR?2. In some situations, the compiler must create a storage location to holdthe value of a call-by-reference parameter. What kinds of parametersmay not have their own storage locations? What actions might berequired in the precall and postcall sequences to handle these actualparameters correctly?7.10SUMMARY AND PERSPECTIVEOne of the more subtle tasks that confronts the compiler writer is selectinga pattern of target-machine operations to implement each source-languageconstruct.
Multiple implementation strategies are possible for almost anysource-language statement. The specific choices made at design time have astrong impact on the code that the compiler generates.In a compiler that is not intended for production use—a debugging compileror a student compiler—the compiler writer might select easy to implement translations for each strategy that produce simple, compact code.