CHAPTER 18:

THE CALL ROUTINES

The term CALL is an acronym for C Arithmetic and Logical Library, the original name for this collection of routines. Actually, however, the CALL routines are not a library but a single module of routines in the library; furthermore, they no longer contain arithmetic routines since the 8086 processor supports all four basic math operations directly. In addition, two of the three types of routines in CALL do not even fit the description. Nevertheless, the name has stuck. So the term CALL is really better understood not as an acronym but as the word call, referring to the fact that these routines are called in lieu of in-line code. The CALL module is always linked to every Small C program.

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		  	8086

primary AX secondary BX stack pointer SP

The 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.

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		Operation

__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

Except 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.

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.

Figure 18-1: Set-up When __Switch is Called

Notice that the return address for the call to __switch is really the address of the switch table, not the address to which control should return; in fact, control should never get there. __Switch will see to it that control goes to one of the case addresses, the default address, or the continuation code.

__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:

  1. The address in the current table entry (pointed to by BX) is moved to CX where it is tested for zero, signaling the end of the table. In that case, a jump instruction takes control to the address in BX (plus 2); that is, to whatever follows the table. If a default prefix exists, then this location holds a jump to the default address. Otherwise, this is the beginning of whatever code follows the switch statement.
  2. The case value at BX+2 is compared to the value of the expression in AX. If they match, a jump to the address in CX transfers control to the code for the matching case prefix. However, if they do not match, the current table address in BX is incremented by four (to the next address/value pair) and control goes back to step 1.

One way or another, control reenters the program. Either it goes to a matching case prefix, the default prefix, or (if there is no default) to whatever follows the switch statement.

Go to Chapter 19 Return to Table of Contents