THE CALL ROUTINES
See Appendix D for a listing of the CALL module. Since the CALL module is written in assembly language, it might be helpful to review the 8086 CPU architecture in Appendix B before proceeding. Also keep in mind that the mapping of Small C registers to 8086 registers is:
Small C 8086The CALL module breaks down into four parts--the start up routine, a short routine for fetching the number of arguments passed to a function, the logical comparison routines, and the switch evaluation routine.primary AX secondary BX stack pointer SP
The Start Up Routine
The first section of code in CALL begins with the label start which is designated, by the end directive (that terminates CALL) as the initial entry point for the program. This is the point at which every Small C program receives control from the operating system. Since this is the only end directive (of all the modules in a program) that specifies an initial entry point there is no ambiguity. When we write modules in assembly language which are to be linked with Small C (or any other language) programs, we must be sure not to specify an initial entry point with the end instruction. One look at the output of the compiler should convince us that the compiler also adheres to this rule.
The start up routine serves four purposes--it sets up the CPU's memory segmentation registers, it sets up the memory heap, it sets up the stack, and it jumps to the function _main() in the library module CSYSLIB. That function then parses the command line arguments, and calls main() in the program. With this arrangement, if control reaches the end of main() it returns back to _main() in CSYSLIB where the statement
exit(0);is waiting to terminate the program gracefully.
See Figure 5-1 for a diagram of the memory arrangement which this routine sets up.
The first thing start does is to set the data segment register (DS) with the paragraph address of the data segment (where the global objects reside).
Note: A paragraph is a 16 byte unit of memory that begins on a 16 byte boundary.
This ensures that the globals will be referenced properly. Next it subtracts this address from the amount of memory that is currently available to the program, to derive the amount of memory in paragraphs that can be used for the heap and the stack. If more than 64K bytes remain then only 64K bytes are used; if less, then whatever remains is used. This number, the length in paragraphs of the combined data/heap/stack segment is added to data segment address to arrive at the value which is placed in the stack pointer (SP). This makes the stack begin at the high end of the segment. Finally, the stack segment register (SS) is set to the same value as DS.
Next, a check is made to ensure that there is enough memory for a minimum stack of 256 bytes. If that should fail, there is not enough memory for the program to even start, so the program aborts with an exit code of 1.
However, if there is enough memory, the global integer _memptr in CSYSLIB is set to the address (offset) of the beginning of the heap. This is where the memory allocation routines keep track of the end of the heap, the beginning of free memory. If this should ever exceed SP a stack overflow occurs.
Last of all, a jump to _main() is executed to continue the start up process in CSYSLIB.
Three instructions are commented out at the end of start. If they were enabled, they would return to DOS the part of available memory that is not being used by the program. If they are used, however, the MS-DOS DEBUG utility will not work. So they have been deactivated.
The Argument Count Routine
_Ccargc is the routine (function), referred to in Chapter 8, that returns a count of the number of arguments passed to a function. Small C places this count in the CL register before calling a function. The called function can get the count by calling CCARGC() before doing anything else. There are only three steps to this routine--move CL to AL, set AH to zero, and return to the caller. The number of arguments passed to the function is then returned in the primary register (AX) where functions normally return their values.
Logical Routines
Ten logical comparison routines reside in the CALL module. They are
Label OperationExcept for the type of comparison made, each routine operates in the same way. It compares BX (left operand) to AX (right operand), and returns one if the condition is true or zero if it is false.__eq equal to __ne not equal to __lt less than __le less than or equal to __ge greater than or equal to __gt greater than __ult unsigned less than __ule unsigned less than or equal to __uge unsigned greater than or equal to __ugt unsigned greater than
A macro instruction called compare is defined and then expanded to produce each routine.
An additional routine __lneg performs the logical negation of the value in AX. If it is true (non-zero), false (zero) is returned; otherwise, true (one) is returned.
By implementing these operations as subroutines, rather than in-line code, we save several bytes per call. This is a size-over-speed compromise.
The Switch Evaluation Routine
__Switch is the routine that evaluates switch conditions. Having a routine for this purpose reduces the size of programs that use switch statements with large numbers of cases, since each case does not require in-line code to compare the case value to the switch expression.
See Chapter 10 for details on the operation of switch statements. When Small C encounters a switch statement, it generates code to evaluate the switch expression and place its value in AX. This is followed by a jump around the code which is associated with the various case prefixes. At that point there is a call to __switch followed by a table of address/value pairs. Each pair gives the address of the code for a case together with the case's value. The table is terminated with a zero address. Next, if a default prefix exists, there is a jump instruction to the default address. Finally, all of this is followed by whatever code comes next in the program. Figure 18-1 illustrates the arrangement.
__Switch pops the table address into BX. This adjusts the stack properly for the jump that transfers control back into the program. That is, the pop...jump sequence is effectively a return. But in this case, one of a number of possible locations is chosen.
Next control enters a loop in which: