
Course Notes 1.2 Fall 2001 94.201 Page 1 of 9 Notes on Subroutines Programs are often required to perform the same operation many times. For example, applications involving integers are often required to display integer values. The code fragment that performs an operation must be executed each time the operation is to be performed. One way to achieve this would be to duplicate the code fragment at each point it is needed. This could work, but might make the program very large. A better solution might be to load one copy of the code fragment into memory as a subroutine, and to provide instructions that allow a program to invoke (i.e. transfer control to) the subroutine whenever the operation is to be performed. At first glance, a simple JMP to the subroutine might seem appropriate; however, the JMP instruction does not include any mechanism to allow control to be returned to the point in the program from which the subroutine was invoked. Subroutines are such a widely used programming concept, that processors include special instructions to support the implementation of subroutines. The p86 processor includes the CALL (invoke) and RET (return) instructions to support subroutines. The execution of the instructions relies on the presence of a stack to hold the return address while the subroutine is executing. Programs wishing to use subroutines must be sure that a stack has been set up using the SP register. The CALL instruction is similar to the JMP instruction, except that the address of the instruction that follows the CALL instruction is pushed onto the stack as part of the CALL execution. The value that is pushed onto the stack is referred to as the return address. This is not difficult to implement, since the address of the next sequential instruction (i.e. the return address) is present in the IP register immediately after fetching the CALL instruction. The CALL instruction uses relative addressing (as does the JMP instruction). The execution of the CALL instruction can be summarized as: PUSH IP JMP target where: target is the relative offset from the instruction after the CALL to the first instruction of the subroutine. The RET instruction returns execution control to the instruction immediately after the CALL instruction that invoked the subroutine. This is accomplished by popping the return address off of the stack and into the IP register. After executing the RET instruction, the next instruction to be executed will be fetched from the return address. The RET instruction will only succeed in accomplishing this objective if the return address is the value on the top of the stack at the time the RET is executed. Subroutines must be careful to ensure that this condition is satisfied! Copyright ã Trevor W. Pearce, October 11, 2000 For use in the 94.201 course only – not for distribution outside of the Department of Systems and Computer Engineering, Carleton University, Ottawa, Canada Course Notes 1.2 Fall 2001 94.201 Page 2 of 9 There are many details associated with subroutines and parameter passing, and there may be several possible ways to deal with the details. By following programming POLICY, the details are handled consistently, which results in programs that are easier to construct, understand and modify. Parameters Parameters allow information to be communicated between the caller (i.e. the code that is invoking the subroutine) and the callee (i.e. the subroutine). Parameters allow subroutines to implement a wider range of operations, which typically results in fewer subroutines being needed in a program. Furthermore, subroutines that have been generalized are often more easily re-used in other applications. For example, a DISPLAY subroutine might be written to display 16-bit integer values in signed ASCII-decimal form. A C++ prototype for the subroutine would be written: void DISPLAY ( int X ) The intention of the subroutine is to display the supplied integer value (represented by the parameter X). An invocation of the subroutine must supply a value for X. When referring to the general description of the subroutine, X is a parameter. When considering a specific invocation of the subroutine, the particular value supplied for X is referred to as the argument. Subroutines are based on assumptions about the purpose of the subroutine. For example, the DISPLAY subroutine described above assumes that the value is to be displayed in signed decimal form. The subroutine might be further generalized by adding an additional parameter that is used to specify the display form. For example, suppose that the prototype for the DISPLAY function was augmented to become: void DISPLAY ( word X, int format ) where: format is an integer value used to represent how the value should be displayed format = 0: signed decimal format = 1: unsigned decimal format = 2: hexadecimal format = 3: binary The augmented DISPLAY function is a further generalization of the previous DISPLAY function. Signed decimal format can still be displayed by passing the format value = 0; but the augment function also handles 3 additional display formats. There are several ways in which parameter information can be communicated (i.e. passed) from the caller to the subroutine: Copyright ã Trevor W. Pearce, October 11, 2000 For use in the 94.201 course only – not for distribution outside of the Department of Systems and Computer Engineering, Carleton University, Ottawa, Canada Course Notes 1.2 Fall 2001 94.201 Page 3 of 9 Global Variables: A global variable is one that is shared by both the caller and the subroutine. The caller communicates information to the subroutine by writing a value into the shared variable before calling the subroutine. The subroutine obtains the information by reading the variable. The variable is referred to as being "global" because the variable's static name (i.e. address) is known both to the caller and the subroutine. The use of global variables is not (generally) considered to be good programming practice; however, we will see in the 94.203 course that there are important cases where global variables are the only practical mechanism for communicating between program components. Registers: Registers may be used to pass information. The caller places arguments in relevant registers before calling the subroutine, and the subroutine assumes that the relevant registers contain arguments. There are two limitations to this approach: First, there are a limited number of registers, and this in turn limits the number of parameters that may be passed via this mechanism. Second, assembly-level programming involves the use of the registers to accomplish program objectives, and therefore, parameters passed in registers must often be saved to memory (perhaps the stack?) to allow the registers to be used by the subroutine. Stack: Parameters may be passed on the stack. The caller must push arguments onto the stack prior to calling the subroutine, and the subroutine must retrieve the arguments from the stack. In this course, we will use the following POLICY: parameters will be passed on the stack. (Passing parameters on the stack is discussed in much more detail below!) In addition to the issue of the physical location (global, register, stack) used to pass parameters, there is also the issue of whether each parameter is passed by value or by reference. When passing by value, a copy of the relevant information is passed. When passing by reference, a reference to a variable containing the relevant information is passed. Passing parameters by reference is often implemented by passing the address of the variable. Whether a parameter is passed by value or reference can be specified in the prototype used to describe the subroutine. As a POLICY in this course, let's assume that the default is to pass by value, and that reference parameters will be prefixed with "&". For example, consider the prototype: void SQUARE( int X, int & XSquared ) The purpose of the SQUARE subroutine is to calculate X*X, and to return the result in the variable XSquared. The parameter X is passed by value, and the parameter XSquared is passed by reference. When setting up to call the subroutine, the caller must supply an integer value as the X argument, and a reference to (i.e. the address of) an integer variable as the XSquared argument. Copyright ã Trevor W. Pearce, October 11, 2000 For use in the 94.201 course only – not for distribution outside of the Department of Systems and Computer Engineering, Carleton University, Ottawa, Canada Course Notes 1.2 Fall 2001 94.201 Page 4 of 9 Parameter Passing on the Stack When passing parameters on the stack, the caller must push the relevant arguments before calling the subroutine. An immediate question that arises is the order in which the parameters are pushed onto the stack. In this course, we will use the following POLICY: each subroutine will be documented by (at least) a C++-like prototype of the subroutine, and the parameters will be pushed onto the stack in right-to-left order of appearance in the prototype. For example, recall the prototype: void SQUARE( int X, int & XSquared ) The XSquared parameter is the rightmost, and the X parameter is the leftmost (as they appear in the prototype); therefore, when calling the SQUARE subroutine, the XSquared argument would be pushed onto the stack before pushing the X argument. Following a call to a subroutine, the stack has the following configuration: SP return address arguments A simple way for the subroutine to access the arguments would be to use register indirect addressing based on the SP value; however, the SP register is not one of the registers permitted for this addressing mode (recall that only BX, BP, SI and DI are permitted).
Details
-
File Typepdf
-
Upload Time-
-
Content LanguagesEnglish
-
Upload UserAnonymous/Not logged-in
-
File Pages9 Page
-
File Size-