16-bit Pragmas

This chapter discusses 16-bit pragmas:

Introduction

A pragma is a compiler directive that provides the following capabilities:

Pragmas are specified in the source file using the pragma directive.

The following classes of pragmas are supported:

Using pragmas to specify options

Currently, the following options can be specified with pragmas:

  • unreferenced
  • check_stack

The unreferenced option controls the way Watcom C/C++ handles unused symbols. For example,

    #pragma on (unreferenced);

causes Watcom C/C++ to issue warning messages for all unused symbols. This is the default. Specifying

    #pragma off (unreferenced);

causes Watcom C/C++ to ignore unused symbols. Note that if the warning level isn't high enough, warning messages for unused symbols aren't issued, even if unreferenced is specified.

The check_stack option controls the way stack overflows are to be handled. For example,

    #pragma on (check_stack);

causes stack overflows to be detected, and

    #pragma off (check_stack);

causes stack overflows to be ignored. When check_stack is on, Watcom C/C++ generates a run-time call to a stack-checking routine at the start of every routine compiled. This run-time routine issues an error if a stack overflow occurs when invoking the routine. The default is to check for stack overflows. Stack-overflow checking is particularly useful when functions are invoked recursively. Note that if the stack overflows, and stack checking has been suppressed, unpredictable results can occur.

If a stack overflow does occur during execution and you're sure that your program isn't in error (that is, it isn't unnecessarily recursing), you must increase the stack size. This is done by linking your application again and specifying the STACK option to the Watcom Linker with a larger stack size. See the section `` The STACK Option'' in the Watcom Linker chapter.

It's also possible to specify more than one option in a pragma, as illustrated by the following example:

    #pragma on (check_stack unreferenced);

Using pragmas to specify default libraries

Default libraries are specified in special object module records. Library names are extracted from these special records by the Watcom Linker. When unresolved references remain after processing all object modules specified in linker FILE directives, these default libraries are searched after all libraries specified in linker LIBRARY directives have been searched.

By default, that is if no library pragma is specified, the Watcom C/C++ compiler generates default libraries corresponding to the memory model and floating-point model used to compile the file, and puts them in the object file defining the main program. For example, if you have compiled the source file containing the main program for the medium memory model and the floating-point calls floating-point model, the libraries clibm and mathm are placed in the object file.

If you wish to add your own default libraries to this list, you can do so with a library pragma. Consider the following example:

    #pragma library (mylib);

The name mylib is added to the list of default libraries specified in the object file.

If the library specification contains characters such as `/', `:' or `,' (that is, any character not allowed in a C identifier), you must enclose it in double quotes, as in the following example:

    #pragma library ("/usr/lib/graph.lib");

If you wish to specify more than one library in a library pragma, you must separate them with spaces, as in the following example:

#pragma library (mylib "/usr/lib/graph.lib");

ALLOC_TEXT pragma (C only)

The alloc_text pragma can be used to specify the name of the text segment into which the generated code for a function or a list of functions is to be placed. The following describes the form of the alloc_text pragma:

    #pragma alloc_text ( seg_name, fn {, fn} ) [;]

where

seg_name
is the name of the text segment.
fn
is the name of a function.

Consider the following example:

    extern int fn1(int);
    extern int fn2(void);
    #pragma alloc_text ( my_text, fn1, fn2 );

The code for the functions fn1() and fn2() is placed in the segment my_text.

Function prototypes for the named functions must exist prior to the alloc_text pragma.

CODE_SEG pragma

The code_seg pragma can be used to specify the name of the text segment in which the generated code for functions is to be placed. The form of the code_seg pragma is as follows:

    #pragma code_seg ( seg_name [, class_name] ) [;]

where

seg_name
is the name of the text segment, enclosed in quotes. Also, seg_name may be a macro, as in:

    #define seg_name "MY_CODE_SEG"
    #pragma code_seg ( seg_name );
      
class_name
is the optional class name of the text segment, enclosed in quotes. Also, class_name may be a macro, as in:

    #define class_name "MY_CLASS"
    #pragma code_seg ( "MY_CODE_SEG", class_name );
      

Consider the following example:

#pragma code_seg ( "my_text" );

int incr( int i )
{
    return( i + 1 );
}

int decr( int i )
{
    return( i - 1 );
}

The code for the functions incr() and decr() is placed in the segment my_text.

COMMENT pragma

The comment pragma can be used to place a comment record in an object file or executable file. The form of the comment pragma is as follows:

#pragma  comment ( comment_type [, "comment_string"] ) [;]

where

comment_type
specifies the type of comment record. The allowable comment types are:
lib
Default libraries are specified in special object module records. Library names are extracted from these special records by the Watcom Linker. When unresolved references remain after processing all object modules specified in linker FILE directives, these default libraries are searched after all libraries specified in linker LIBRARY directives have been searched.

The lib form of this pragma offers the same features as the library pragma. See the section entitled ``Using Pragmas to Specify Default Libraries'' for more information.

comment_string
is an optional string literal that provides additional information for some comment types.

For example,

    #pragma comment ( lib, "mylib" );

DATA_SEG pragma

The data_seg pragma can be used to specify the name of the segment into which data is to be placed. The form of the data_seg pragma is as follows:

    #pragma  data_seg ( seg_name [, class_name] ) [;]

where

seg_name
is the name of the data segment, enclosed in quotes. Also, seg_name may be a macro, as in:

    #define seg_name "MY_DATA_SEG"
    #pragma data_seg ( seg_name );
      
class_name
is the optional class name of the data segment, enclosed in quotes. Also, class_name may be a macro, as in:

    #define class_name "MY_CLASS"
    #pragma data_seg ( "MY_DATA_SEG", class_name );
      

Consider the following example:

    #pragma data_seg ( "my_data" );

    static int i;
    static int j;

The variables i and j are placed in the segment my_data.

DISABLE_MESSAGE pragma (C only)

The disable_message pragma disables the issuance of specified diagnostic messages. The form of the disable_message pragma is as follows:

    #pragma disable_message ( msg_num {, msg_num} ) [;]

where

msg_num
is the number of the diagnostic message. This number corresponds to the number issued by the compiler, and can be found in Appendix B: C Diagnostic Messages. Make sure to strip all leading zeroes from the message number to avoid interpretation as an octal constant.

See also the description of the ENABLE_MESSAGE pragma.

DUMP_OBJECT_MODEL pragma (C++ only)

The dump_object_model pragma causes the C++ compiler to print information about the object model for an indicated class to the diagnostics file. This information includes the offsets and sizes of fields within the class and within base classes.

The general form of the dump_object_model pragma is as follows:

    #pragma dump_object_model class [;]

where

class
is a defined C++ class free of errors

This pragma is designed to be used for information purposes only.

ENABLE_MESSAGE pragma (C only)

The enable_message pragma re-enables the issuance of specified diagnostic messages that have been previously disabled. The form of the enable_message pragma is as follows:

    #pragma enable_message ( msg_num {, msg_num} ) [;]

where

msg_num
is the number of the diagnostic message. This number corresponds to the number issued by the compiler, and can be found in Appendix B: C Diagnostic Messages. Make sure to strip all leading zeroes from the message number to avoid interpretation as an octal constant.

See also the description of the DISABLE_MESSAGE pragma.

ERROR pragma

The error pragma can be used to issue an error message with the specified text. The form of the error pragma is as follows:

    #pragma error "error text" [;]

where

error text
is the text of the message that you wish to display, enclosed in quotes.
You should use the ANSI #error directive rather than this pragma. This pragma is provided for compatibility with legacy code.

For example,

#if defined(__386__)
    ...
#elseif defined(__86__)
    ...
#else
#pragma error ( "neither __386__ or __86__ defined" );
#endif

FUNCTION pragma

Certain functions, listed in the description of the oi option, have intrinsic forms. These functions are special functions that are recognized by the compiler and processed in a special way. For example, the compiler might choose to generate in-line code for the function. The intrinsic attribute for these special functions is set by specifying the oi option, or by using an intrinsic pragma. The function pragma can be used to remove the intrinsic attribute for a specified list of functions.

The form of the function pragma is as follows:

    #pragma function ( fn {, fn} ) [;]

where

fn
is the name of a function.

Suppose the following source code is compiled using the oi option so that when one of the special functions is referenced, the intrinsic form is used. In our example, we have referenced the function sin(), which does have an intrinsic form. By specifying sin in a function pragma, the intrinsic attribute is removed, causing the function sin() to be treated as a regular user-defined function.

#include <math.h>
#pragma function( sin );

double test( double x )
{
    return( sin( x ) );
}

Setting priority of static data initialization (C++ only)

The initialize pragma sets the priority for initialization of static data in the file. This priority only applies to initialization of static data that requires the execution of code. For example, the initialization of a class that contains a constructor requires the execution of the constructor. Note that if the sequence in which initialization of static data in your program takes place has no dependencies, you don't need to use the initialize pragma.

The general form of the initialize pragma is as follows:

    #pragma initialize [before | after] priority [;]

where priority is defined as

    n | library | program

The settings for priority are as follows:

n
is a number representing the priority. This must be in the range 0-255. The larger the priority, the later the point at which initialization will occur.

Priorities in the range 0-20 are reserved for the C++ compiler. This is to ensure that proper initialization of the C++ run-time system takes place before the execution of your program.

library
represents a priority of 32, and can be used for class libraries that require initialization before the program is initialized.
program
represents a priority of 64, and is the default priority for any compiled code.

Specifying before adjusts the priority by subtracting one. Specifying after adjusts the priority by adding one.

A source file containing the following initialize pragma specifies that the initialization of static data in the file will take place before initialization of all other static data in the program, since a priority of 63 is assigned.

    #pragma initialize before program

If you specify after instead of before, the initialization of the static data in the file will occur after initialization of all other static data in the program, since a priority of 65 is assigned.

Note that the following is equivalent to the before example

    #pragma initialize 63

and the following is equivalent to the after example.

    #pragma initialize 65
Use the before, after, library and program keywords; they're more descriptive, and make the intent of the pragma more obvious.

You should use a priority of 32 (the priority used when the library keyword is specified) when developing class libraries. This ensures that static data defined by the class library is initialized before static data defined by the program. The following initialize pragma can be used to do this:

    #pragma initialize library

INLINE_DEPTH pragma (C++ only)

When an in-line function is called, the function call may be replaced by the in-line expansion for that function. This in-line expansion might include calls to other in-line functions, which can also be expanded. The inline_depth pragma can be used to set the number of times this expansion of in-line functions will occur for a call.

The form of the inline_depth pragma is as follows:

    #pragma inline_depth [(] n [)] [;]

where

n
is the depth of expansion:
  • If n is 0, no expansion occurs.
  • If n is 1, only the original call is expanded.
  • If n is 2, the original call and the in-line functions invoked by the original function are expanded.

The default value for n is 8; the maximum is 255.

No expansion of recursive in-line functions occurs unless it's enabled using the inline_recursion pragma.

INLINE_RECURSION pragma (C++ only)

The inline_recursion pragma controls the recursive expansion of in-line functions. The form of the inline_recursion pragma is as follows:

    #pragma inline_recursion [(] on [)] [;]

or

    #pragma inline_recursion [(] off [)] [;]

Specifying on enables the expansion of recursive in-line functions. The depth of expansion is specified by the inline_depth pragma. The default depth is 8.

Specifying off suppresses the expansion of recursive in-line functions. This is the default.

INTRINSIC pragma

Certain functions, listed in the description of the oi option, have intrinsic forms. These functions are special functions that are recognized by the compiler and processed in a special way. For example, the compiler may choose to generate in-line code for the function. The intrinsic attribute for these special functions is set by specifying the oi option, or by using an intrinsic pragma.

The following describes the form of the intrinsic pragma:

    #pragma intrinsic ( fn {, fn} ) [;]

where

fn
is the name of a function.

Suppose the following source code is compiled without using the oi option, so that no function has the intrinsic attribute. If we want the intrinsic form of the sin() function to be used, we could specify the function in an intrinsic pragma, as follows:

#include <math.h>
#pragma intrinsic( sin );

double test( double x )
{
    return( sin( x ) );
}

MESSAGE pragma

The message pragma can be used to issue a message with the specified text to the standard output, without terminating compilation. The following describes the form of the message pragma.

    #pragma message ( "message text" ) [;]

where

message text
is the text of the message that you wish to display, enclosed in quotes.

For example,

#if defined(__386__)
    ...
#else
#pragma message ( "assuming 16-bit compile" );
#endif

PACK pragma

The pack pragma can be used to control the way in which structures are stored in memory. By default, Watcom C/C++ aligns all structures and its fields on a byte boundary. There are 4 forms of the pack pragma. They're used to:

Changing the alignment

This form of the pack pragma can be used to change the alignment of structures and their fields in memory.

    #pragma pack ( n ) [;]

where

n
is 1, 2, 4 or 8, and specifies the method of alignment.

The alignment of structure members is described in the following table. If the size of the member is 1, 2, 4 or 8, the alignment is given for each of the zp options.

sizeof(member) zp1 zp2 zp4 zp8
10000
20222
40244
80248

An alignment of 0 means no alignment, 2 means alignment on a word boundary, 4 means a double-word boundary, and so on.

If the member of the structure is an array or structure, it's aligned to the largest member, as follows:

  • If the largest member of structure x is 1 byte, then x isn't aligned.
  • If the largest member of structure x is 2 bytes, then x is aligned according to row 2.
  • If the largest member of structure x is 4 bytes, then x is aligned according to row 4.
  • If the largest member of structure x is 8 bytes, then x is aligned according to row 8.

If no value is specified in the pack pragma, a default value of 1 is used. Note that the default value can be changed with the zp Watcom C/C++ compiler command line option.

Saving the alignment

This form of the pack pragma can be used to save the current alignment amount on an internal stack:

    #pragma  pack ( push ) [;]

Saving and setting the alignment

This form of the pack pragma can be used to save the current alignment amount on an internal stack and set the current alignment:

    #pragma pack ( push, number ) [;]

Restoring the alignment

This form of the pack pragma can be used to restore the previous alignment amount from an internal stack:

    #pragma  pack ( pop ) [;]

TEMPLATE_DEPTH pragma (C++ only)

You can define a C++ template that uses other template classes. These templates can use other templates in their definitions, and so on. This pragma can be used to limit the depth of such nestings.

The form of the template_depth pragma is as follows:

    #pragma template_depth [(] n [)] [;]

where

n
is the depth of expansion:
  • If n is 0, no expansion will occur.
  • If n is 1, only the original call is expanded.
  • If n is 2, the original call and the in-line functions invoked by the original function are expanded.

The default value for n is 8; the maximum is 255.

WARNING pragma (C++ only)

The warning pragma sets the level of warning messages. The form of the warning pragma is as follows:

    #pragma  warning msg_num level [;]

where

msg_num
is the number of the warning message. This number corresponds to the number issued by the compiler, and can be found in Appendix C: C++ Diagnostic Messages. If msg_num is ``*'', the level of all warning messages is changed to the specified level. Make sure to strip all leading zeroes from the message number to avoid interpretation as an octal constant.
level
is a number from 0 to 9, and represents the level of the warning message. When a value of zero is specified, the warning becomes an error.

Auxiliary pragmas

The following sections describe the capabilities provided by auxiliary pragmas:

Specifying symbol attributes

Auxiliary pragmas are used to describe attributes that affect code generation. Initially, the compiler defines a default set of attributes. Each auxiliary pragma refers to one of the following:

  • a symbol (such as a variable or function)
  • a type definition that resolves to a function type
  • the default set of attributes defined by the compiler

When an auxiliary pragma refers to a particular symbol, a copy of the current set of default attributes is made and merged with the attributes specified in the auxiliary pragma. The resulting attributes are assigned to the specified symbol, and can only be changed by another auxiliary pragma that refers to the same symbol.

An example of a type definition that resolves to a function type is as follows:

    typedef void (*func_type)();

When an auxiliary pragma refers to a such a type definition, a copy of the current set of default attributes is made and merged with the attributes specified in the auxiliary pragma. The resulting attributes are assigned to each function whose type matches the specified type definition.

When default is specified instead of a symbol name, the attributes specified by the auxiliary pragma change the default set of attributes. The resulting attributes are used by all symbols that haven't been specifically referenced by a previous auxiliary pragma.

Note that all auxiliary pragmas are processed before code generation begins. Consider the following example:

/* code in which symbol x is referenced */
    .
    .
    .

#pragma aux y attrs_1;

/* code in which symbol y is referenced */
    .
    .
    .

/* code in which symbol z is referenced */
    .
    .
    .

#pragma aux default attrs_2;
#pragma aux x attrs_3;

Auxiliary attributes are assigned to x, y and z in the following way:

  1. Symbol x is assigned the initial default attributes merged with the attributes specified by attrs_2 and attrs_3.
  2. Symbol y is assigned the initial default attributes merged with the attributes specified by attrs_1.
  3. Symbol z is assigned the initial default attributes merged with the attributes specified by attrs_2.

Alias names

When a symbol referred to by an auxiliary pragma includes an alias name, the attributes of the alias name are also assumed by the specified symbol.

There are two methods of specifying alias information:

  • In the first method, the symbol assumes only the attributes of the alias name; no additional attributes can be specified.
  • The second method is more general, since it's possible to specify an alias name as well as additional auxiliary information. In this case, the symbol assumes the attributes of the alias name as well as the attributes specified by the additional auxiliary information.

Simple form for aliases

The simple form of the auxiliary pragma used to specify an alias is as follows:

    #pragma aux ( sym, alias ) [;]

where

sym
is any valid C/C++ identifier.
alias
is the alias name, and is any valid C/C++ identifier.

Consider the following example:

    #pragma aux push_args parm [] ;
    #pragma aux ( rtn, push_args ) ;

The routine rtn() assumes the attributes of the alias name push_args, which specifies that the arguments to rtn() are passed on the stack.

Let's look at an example in which the symbol is a type definition.

typedef void (func_type)(int);

#pragma aux push_args parm [];
#pragma aux ( func_type, push_args );

extern func_type rtn1;
extern func_type rtn2;

The first auxiliary pragma defines an alias name called push_args that specifies the mechanism to be used to pass arguments. The mechanism is to pass all arguments on the stack. The second auxiliary pragma associates the attributes specified in the first pragma with the type definition func_type. Since rtn1() and rtn2() are of type func_type, arguments to either of these functions are passed on the stack.

General form for aliases

The general form of an auxiliary pragma that can be used to specify an alias is as follows:

    #pragma aux ( alias ) sym aux_attrs [;]

where

alias
is the alias name, and is any valid C/C++ identifier.
sym
is any valid C/C++ identifier.
aux_attrs
are attributes that can be specified with the auxiliary pragma.

Consider the following example:

#pragma aux MS_C "_*"                                   \
                 parm caller []                         \
                 value struct float struct routine [ax] \
                 modify [ax bx cx dx es];
#pragma aux (MS_C) rtn1;
#pragma aux (MS_C) rtn2;
#pragma aux (MS_C) rtn3;

The routines rtn1(), rtn2() and rtn3() assume the same attributes as the alias name MS_C, which defines the calling convention used by the Microsoft C compiler. Whenever calls are made to rtn1(), rtn2() and rtn3(), the Microsoft C calling convention is used.

Note that if the attributes of MS_C change, only one pragma needs to be changed. If we hadn't used an alias name, and specified the attributes in each of the three pragmas for rtn1(), rtn2() and rtn3(), we would have to change all three pragmas. This approach also reduces the amount of memory required by the compiler to process the source file.

The alias name MS_C is just another symbol. If MS_C appeared in your source code, it would assume the attributes specified in the pragma for MS_C.

Predefined aliases

A number of symbols are predefined by the compiler with a set of attributes that describe a particular calling convention. These symbols can be used as aliases. The following is a list of these symbols.

__cdecl
__cdecl or cdecl defines the calling convention used by Microsoft compilers.
__pascal
__pascal or pascal defines the calling convention used by OS/2 1.x and Windows 3.x API functions.

The following sections describe the attributes of the above alias names:

Predefined __cdecl alias

    #pragma aux __cdecl "_*"                            \
                parm caller []                          \
                value struct float struct routine [ax] \
                modify [ax bx cx dx es]

Note the following:

  1. All symbols are preceded by an underscore character.
  2. Arguments are pushed on the stack from right to left. That is, the last argument is pushed first. The calling routine will remove the arguments from the stack.
  3. Floating-point values are returned in the same way as structures. When a structure is returned, the called routine allocates space for the return value, and returns a pointer to the return value in register AX.
  4. Registers AX, BX, CX and DX, and segment register ES aren't saved and restored when a call is made.

Predefined __pascal alias

#pragma aux __pascal "^"                       \
           parm reverse routine []             \
           value struct float struct caller [] \
           modify [ax bx cx dx es]

Note the following:

  1. All symbols are mapped to upper case.
  2. Arguments are pushed on the stack in reverse order. That is, the first argument is pushed first, the second argument is pushed next, and so on. The routine being called will remove the arguments from the stack.
  3. Floating-point values are returned in the same way as structures. When a structure is returned, the caller allocates space on the stack. The address of the allocated space is pushed on the stack immediately before the call instruction. Upon returning from the call, register AX contains the address of the space allocated for the return value.
  4. Registers AX, BX, CX and DX, and segment register ES aren't saved and restored when a call is made.

Alternate names for symbols

The following form of the auxiliary pragma can be used to describe the mapping of a symbol from its source form to its object form:

    #pragma aux sym obj_name [;]

where

sym
is any valid C/C++ identifier.
obj_name
is any character string, enclosed in double quotes.

When specifying obj_name, the asterisk character (*) has a special meaning; it's a placeholder for the upper case version of sym.

In the following example, the name myrtn is replaced by myrtn_ in the object file.

    #pragma aux myrtn "*_";

This is the default for all function names.

In the following example, the name myvar is replaced by _myvar in the object file.

    #pragma aux myvar "_*";

This is the default for all variable names.

The default mapping for all symbols can also be changed as illustrated by the following example:

    #pragma aux default "_*_";

The above auxiliary pragma specifies that all names are prefixed and suffixed by an underscore character (`_').

The `^' character also has a special meaning. Whenever it's encountered in obj_name, it's replaced by the upper-case version of sym.

In the following example, the name myrtn is replaced by MYRTN in the object file.

    #pragma aux myrtn "^";

Describing calling information

The following form of the auxiliary pragma can be used to describe the way a function is to be called:

    #pragma  aux sym far [;]

or

    #pragma aux sym near [;]

or

    #pragma aux sym = in_line [;]

The in-line variable is defined as:

    { const | (seg id) | (offset id) | (reloff id)
            | (float fpinst) | "asm" }

where

sym
is a function name
const
is a valid C/C++ integer constant.
id
is any valid C/C++ identifier.
fpinst
is a sequence of bytes that forms a valid 80x87 instruction. The keyword float must precede fpinst so that special fixups are applied to the 80x87 instruction.
seg
specifies the segment of the symbol id.
offset
specifies the offset of the symbol id.
reloff
specifies the relative offset of the symbol id for near control transfers.
asm
is an assembly language instruction or directive.

In the following example, Watcom C/C++ generates a far call to the function myrtn().

    #pragma aux myrtn far;
This overrides the calling sequence that would normally be generated for a particular memory model. In other words, a far call is generated, even if you're compiling for a memory model with a small code model.

In the following example, Watcom C/C++ generates a near call to the function myrtn().

    #pragma aux myrtn near;
This overrides the calling sequence that would normally be generated for a particular memory model. In other words, a near call is generated even if you're compiling for a memory model with a big code model.

In the following DOS example, Watcom C/C++ generates the sequence of bytes following the ``='' character in the auxiliary pragma whenever a call to mode4() is encountered. mode4() is called an in-line function.

void mode4(void);
#pragma aux mode4 =                   \
    0xb4 0x00        /* mov AH,0 */   \
    0xb0 0x04        /* mov AL,4 */   \
    0xcd 0x10        /* int 10H  */   \
    modify [ AH AL ];

The sequence in the above DOS example represents the following lines of assembly language instructions:

mov   AH,0     ; select function "set mode"
mov   AL,4     ; specify mode (mode 4)
int   10H      ; BIOS video call

The above example demonstrates how to generate BIOS function calls in-line without writing an assembly language function and calling it from your C/C++ program. The C prototype for the function mode4() isn't necessary but is included so that we can take advantage of the argument type checking provided by Watcom C/C++.

The following DOS example is equivalent to the above example, but mnemonics for the assembly language instructions are used instead of the binary encoding of the assembly language instructions.

void mode4(void);
#pragma aux mode4 =    \
    "mov AH,0",        \
    "mov AL,4",        \
    "int 10H"          \
    modify [ AH AL ];

If a sequence of in-line assembly language instructions contains 80x87 floating-point instructions, each floating-point instruction must be preceded by float. Note that this is only required if you have specified the fpi compiler option; otherwise it's ignored.

The following example generates the 80x87 square-root instruction.

double mysqrt(double);
#pragma aux mysqrt parm [8087] = \
    float 0xd9 0xfa /* fsqrt */;

A sequence of in-line assembly language instructions may contain symbolic references. In the following example, a near call to the function myalias() is made whenever myrtn() is called.

extern void myalias(void);
void myrtn(void);
#pragma aux myrtn =            \
    0xe8 offset myalias /* near call */;

In the following example, a far call to the function myalias() is made whenever myrtn() is called.

extern void myalias(void);
void myrtn(void);
#pragma aux myrtn =                   \
    0x9a offset myalias seg myalias /* far call */;

Loading data segment register

An application may have been compiled so that the segment register DS doesn't contain the segment address of the default data segment (group DGROUP). This is usually the case if you're using a large data memory model. Suppose you wish to call a function that assumes that the segment register DS contains the segment address of the default data segment. It would be very cumbersome if you were forced to compile your application so that the segment register DS contained the default data segment (a small data memory model).

The following form of the auxiliary pragma causes the segment register DS to be loaded with the segment address of the default data segment before calling the specified function.

    #pragma aux sym parm loadds [;]

where

sym
is a function name.

Alternatively, the following form of the auxiliary pragma causes the segment register DS to be loaded with the segment address of the default data segment as part of the prologue sequence for the specified function.

    #pragma  aux sym loadds [;]

where

sym
is a function name.

Defining exported symbols in dynamic link libraries

An exported symbol in a dynamic link library is a symbol that can be referenced by an application that is linked with that dynamic link library. Normally, symbols in dynamic link libraries are exported using the Watcom Linker EXPORT directive. An alternative method is to use the following form of the auxiliary pragma:

    #pragma aux sym export [;]

where

sym
is a function name.

Defining windows callback functions

When compiling a Microsoft Windows application, you must use the zW option so that special prologue/epilogue sequences are generated. Furthermore, callback functions require larger prologue/epilogue sequences than those generated when the zW compiler option is specified. The following form of the auxiliary pragma causes a callback prologue/epilogue sequence to be generated for a callback function when compiled using the zW option.

    #pragma  aux sym export [;]

where

sym
is a callback function name.

Alternatively, the zw compiler option can be used to generate callback prologue/epilogue sequences. However, all functions contained in a module compiled using the zw option will have a callback prologue/epilogue sequence, even if the functions aren't callback functions.

Forcing a stack frame

Normally, a function contains a stack frame if arguments are passed on the stack, or an automatic variable is allocated on the stack. No stack frame is generated if the above conditions aren't satisfied. The following form of the auxiliary pragma forces a stack frame to be generated under any circumstance.

    #pragma aux sym frame [;]

where

sym
is a function name.

Describing argument information

Using auxiliary pragmas, you can describe the calling convention that Watcom C/C++ is to use for calling functions. This is particularly useful when interfacing to functions that have been compiled by other compilers, or functions written in other programming languages.

The general form of an auxiliary pragma that describes argument passing is as follows:

    #pragma aux sym parm { pop_info | reverse | {reg_set} } [;]

The pop_info variable is defined as:

    caller | routine

where

sym
is a function name.
reg_set
is called a register set. The register sets specify the registers that are to be used for argument passing. A register set is a list of registers separated by spaces and enclosed in square brackets.

Passing arguments in registers

The following form of the auxiliary pragma can be used to specify the registers that are to be used to pass arguments to a particular function:

    #pragma aux sym parm {reg_set} [;]

where

sym
is a function name.
reg_set
is called a register set. The register sets specify the registers that are to be used for argument passing. A register set is a list of registers separated by spaces and enclosed in square brackets.

Register sets establish a priority for register allocation during argument list processing. Register sets are processed from left to right. However, within a register set, registers are chosen in any order. Once all register sets have been processed, any remaining arguments are pushed on the stack.

Note that regardless of the register sets specified, only certain combinations of registers are selected for arguments of a particular type:

  1. 8-byte arguments (arguments of type double) can only be passed in the following register combination: AX:BX:CX:DX. Note that this doesn't include 8-byte structures. For example, if the following register set is specified for a routine having an argument of type double,

        [AX BX SI DI]
        
    

    the argument is pushed on the stack, since a valid register combination for 8-byte arguments isn't contained in the register set.

  2. A far pointer can only be passed in one of the following register pairs: DX:AX, CX:BX, CX:AX, CX:SI, DX:BX, DI:AX, CX:DI, DX:SI, DI:BX, SI:AX, CX:DX, DX:DI, DI:SI, SI:BX, BX:AX, DS:CX, DS:DX, DS:DI, DS:SI, DS:BX, DS:AX, ES:CX, ES:DX, ES:DI, ES:SI, ES:BXor ES:AX. For example, if a far pointer is passed to a function with the following register set:

        [ES BP]
        
    

    the argument is pushed on the stack, since a valid register combination for a far pointer isn't contained in the register set.

  3. The only registers that are assigned to 4-byte arguments (that is, arguments of type long int) are: DX:AX, CX:BX, CX:AX, CX:SI, DX:BX, DI:AX, CX:DI, DX:SI, DI:BX, SI:AX, CX:DX, DX:DI, DI:SI, SI:BX and BX:AX. For example, if the following register set is specified for a routine with one argument of type long int:

        [ES DI]
        
    

    the argument is pushed on the stack, since a valid register combination for 4-byte arguments isn't contained in the register set.

  4. The only registers that are assigned to 2-byte arguments (that is, arguments of type int) are: AX, BX, CX, DX, SI and DI. For example, if the following register set is specified for a routine with one argument of type int,

        [BP]
        
    

    the argument is pushed on the stack, since a valid register combination for 2-byte arguments isn't contained in the register set.

  5. Arguments whose size is 1 byte (arguments of type char) are promoted to 2 bytes, and are then assigned registers as if they were 2-byte arguments.
  6. Arguments that don't fall into one of the above categories cannot be passed in registers, and are passed on the stack. Once an argument has been assigned a position on the stack, all remaining arguments are assigned a position on the stack even if all register sets haven't yet been exhausted.

Note the following:

  1. The default register set is [AX BX CX DX].
  2. Specifying certain pairs of registers is the same as specifying another register, as shown below.
    Register pair Equivalent register
    AH and ALAX
    DH and DLDX
    CH and CLCX
    BH and BLBX
  3. If you're compiling for a memory model with a small data model, or the zdp compiler option is specified, any register combination containing register DS becomes illegal. In a small data model, segment register DS must remain unchanged, as it points to the program's data segment. Note that the zdf compiler option can be used to specify that register DS doesn't contain the segment address of the program's data segment. In this case, register combinations containing register DS are legal.

Consider the following example:

    #pragma aux myrtn parm [ax bx cx dx] [bp si];

Suppose myrtn() is a routine with 3 arguments, each of type long int.

  1. The first argument is passed in the register pair DX:AX.
  2. The second argument is passed in the register pair CX:BX.
  3. The third argument is passed on the stack, since BP:SI is not a valid register pair for arguments of type long int.

It's possible for registers from the second register set to be used before registers from the first register set are used. Consider the following example:

    #pragma aux myrtn parm [ax bx cx dx] [si di];

Suppose myrtn() is a routine with 3 arguments, the first of type int, and the second and third of type long int.

  1. The first argument is passed in the register AX.
  2. The second argument is passed in the register pair CX:BX.
  3. The third argument is passed in the register set DI:SI.

Note that registers are no longer selected from a register set after registers are selected from subsequent register sets, even if all registers from the original register set haven't been exhausted.

An empty register set is permitted. All subsequent register sets appearing after an empty register set are ignored; all remaining arguments are pushed on the stack.

Note the following:

  1. If a single empty register set is specified, all arguments are passed on the stack.
  2. If no register set is specified, the default register set [AX BX CX DX] is used.

Forcing arguments into specific registers

It's possible to force arguments into specific registers. Suppose you have a function mycopy() that copies data. The first argument is the source, the second argument is the destination, and the third argument is the length to copy. If we want the first argument to be passed in the register SI, the second argument to be passed in register DI, and the third argument to be passed in register CX, the following auxiliary pragma can be used.

void mycopy( char near *, char *, int );
#pragma aux mycopy parm [DI] [SI] [CX];

Note that you must be aware of the size of the arguments to ensure that the arguments get passed in the appropriate registers.

Passing arguments to in-line functions

For functions whose code is generated by Watcom C/C++, and whose argument list is described by an auxiliary pragma, Watcom C/C++ has some freedom in choosing how arguments are assigned to registers. Since the code for in-line functions is specified by the programmer, the description of the argument list must be very explicit. To achieve this, Watcom C/C++ assumes that each register set corresponds to an argument. Consider the following DOS example of an in-line function called scrollactivepgup().

void scrollactivepgup(char,char,char,char,char,char);
#pragma aux scrollactivepgup = \
    "mov AH,6"     \
    "int 10h"      \
    parm [ch] [cl] [dh] [dl] [al] [bh] \
    modify [ah];

The BIOS video call to scroll the active page up requires the following arguments:

  1. The row and column of the upper left corner of the scroll window are passed in registers CH and CL, respectively.
  2. The row and column of the lower right corner of the scroll window are passed in registers DH and DL, respectively.
  3. The number of lines blanked at the bottom of the window is passed in register AL.
  4. The attribute to be used on the blank lines is passed in register BH.

When passing arguments, Watcom C/C++ converts the argument so that it fits in the register(s) specified in the register set for that argument. For example, if scrollactivepgup() is called with a first argument of type int, it's first converted to char before assigning it to register CH. Similarly, if an in-line function requires its argument in the register pair DX:AX, and the argument is of type short int, the argument is converted to long int before assigning it to the register pair DX:AX.

In general, Watcom C/C++ assigns the following types to register sets:

  • A register set consisting of a single 8-bit register (1 byte) is assigned a type of unsigned char.
  • A register set consisting of a single 16-bit register (2 bytes) is assigned a type of unsigned short int.
  • A register set consisting of two 16-bit registers (4 bytes) is assigned a type of unsigned long int.
  • A register set consisting of four 16-bit registers (8 bytes) is assigned a type of double.

Removing arguments from the stack

The following form of the auxiliary pragma specifies who removes from the stack arguments that were passed on the stack.

    #pragma aux sym parm (caller | routine) [;]

where

sym
is a function name.

The caller keyword specifies that the caller will pop the arguments from the stack; routine specifies that the called routine will pop the arguments from the stack. If caller or routine is omitted, routine is assumed, unless the default has been changed in a previous auxiliary pragma, in which case the new default is assumed.

Passing arguments in reverse order

The following form of the auxiliary pragma specifies that arguments are passed in the reverse order:

    #pragma aux sym parm reverse [;]

where

sym
is a function name.

Normally, arguments are processed from left to right. The leftmost arguments are passed in registers and the rightmost arguments are passed on the stack (if the registers used for argument passing have been exhausted). Arguments that are passed on the stack are pushed from right to left.

When arguments are reversed, the rightmost arguments are passed in registers and the leftmost arguments are passed on the stack (if the registers used for argument passing have been exhausted). Arguments that are passed on the stack are pushed from left to right.

Reversing arguments is most useful for functions that require arguments to be passed on the stack in an order opposite from the default. The following auxiliary pragma demonstrates such a function:

    #pragma aux rtn parm reverse [];

Describing function return information

Using auxiliary pragmas, you can describe the way functions are to return values. This is particularly useful when interfacing to functions that have been compiled by other compilers, or functions written in other programming languages.

The general form of an auxiliary pragma that describes the way a function returns its value is as follows:

    #pragma aux sym value {no8087 | reg_set | struct_info} [;]

The struct_info variable is defined as

    struct {float | struct | (routine | caller) | reg_set}

where

sym
is a function name.
reg_set
is called a register set. The register sets specify the registers that are to be used for argument passing. A register set is a list of registers separated by spaces and enclosed in square brackets.

Returning function values in registers

The following form of the auxiliary pragma can be used to specify the registers that are to be used to return a function's value:

    #pragma  aux sym value reg_set [;]

where

sym
is a function name.
reg_set
is a register set.

Depending on the type of the return value, only certain registers are allowed in reg_set:

  • For 1-byte return values, only the following registers are allowed: AL, AH, DL, DH, BL, BH, CL or CH. If no register set is specified, register AL is used.
  • For 2-byte return values, only the following registers are allowed: AX, DX, BX, CX, SI or DI. If no register set is specified, register AX is used.
  • For 4-byte return values (except far pointers), only the following register pairs are allowed: DX:AX, CX:BX, CX:AX, CX:SI, DX:BX, DI:AX, CX:DI, DX:SI, DI:BX, SI:AX, CX:DX, DX:DI, DI:SI, SI:BX or BX:AX. If no register set is specified, registers DX:AX are used.
  • For functions that return far pointers, the following register pairs are allowed: DX:AX, CX:BX, CX:AX, CX:SI, DX:BX, DI:AX, CX:DI, DX:SI, DI:BX, SI:AX, CX:DX, DX:DI, DI:SI, SI:BX, BX:AX, DS:CX, DS:DX, DS:DI, DS:SI, DS:BX, DS:AX, ES:CX, ES:DX, ES:DI, ES:SI, ES:BX or ES:AX. If no register set is specified, the registers DX:AX are used.
  • For 8-byte return values (functions of type double), only the following register combination is allowed: AX:BX:CX:DX. If no register set is specified, the registers AX:BX:CX:DX are used.

Note the following:

  1. An empty register set isn't allowed.
  2. If you're compiling for a memory model that has a small data model, any of the above register combinations containing register DS becomes illegal. In a small data model, segment register DS must remain unchanged, as it points to the program's data segment.

Returning structures

Typically, structures aren't returned in registers. Instead, the caller allocates space on the stack for the return value, and sets register SI to point to it. The called routine then places the return value at the location pointed to by register SI.

The following form of the auxiliary pragma can be used to specify the register that is to be used to point to the return value:

    #pragma aux sym value struct (caller|routine) reg_set [;]

where

sym
is a function name.
reg_set
is a register set.

The caller keyword specifies that the caller will allocate memory for the return value. The address of the memory allocated for the return value is placed in the register specified in the register set by the caller before the function is called. If an empty register set is specified, the address of the memory allocated for the return value is pushed on the stack immediately before the call and is returned in register AX by the called routine. It's assumed that the memory for the return value is allocated from the stack segment (the stack segment is contained in segment register SS).

The routine keyword specifies that the called routine will allocate memory for the return value. Upon returning to the caller, the register specified in the register set will contain the address of the return value. An empty register set isn't allowed.

Only the following registers are allowed in the register set: AX, DX, BX, CX, SI or DI. Note that in a big data model, the address in the return register is assumed to be in the segment specified by the value in the SS segment register.

If the size of the structure being returned is 1, 2 or 4 bytes, it's returned in registers. The return register is selected from the register set in the following way:

  1. A 1-byte structure is returned in one of the following registers: AL, AH, DL, DH, BL, BH, CL or CH. If no register set is specified, register AL is used.
  2. A 2-byte structure is returned in one of the following registers: AX, DX, BX, CX, SI or DI. If no register set is specified, register AX is used.
  3. A 4-byte structure is returned in one of the following register pairs: DX:AX, CX:BX, CX:AX, CX:SI, DX:BX, DI:AX, CX:DI, DX:SI, DI:BX, SI:AX, CX:DX, DX:DI, DI:SI, SI:BX or BX:AX. If no register set is specified, register pair DX:AX is used.

The following form of the auxiliary pragma can be used to specify that structures whose size is 1, 2 or 4 bytes aren't to be returned in registers. Instead, the caller allocates space on the stack for the structure return value and points register SI to it.

    #pragma aux sym value struct struct [;]

where

sym
is a function name.

Returning floating-point data

The following form of the auxiliary pragma can be used to specify that function return values of type float or double aren't to be returned in registers. Instead, the caller allocates space on the stack for the return value and points register SI to it:

    #pragma aux sym value struct float [;]

where

sym
is a function name.

In other words, floating-point values are to be returned in the same way structures are returned.

The following form of the auxiliary pragma can be used to specify that function return values of type float or double aren't to be returned in 80x87 registers when compiling with the fpi or fpi87 option. Instead, the value is returned in 80x86 registers. Function return values of type float are returned in registers DX:AX. Function return values of type double are returned in registers AX:BX:CX:DX.

    #pragma aux sym value no8087 [;]

where

sym
is a function name.

The following form of the auxiliary pragma can be used to specify that function return values of type float or double are to be returned in ST(0) when compiling with the fpi or fpi87 option.

    #pragma aux sym value [8087] [;]

where

sym
is a function name.

A function that never returns

The following form of the auxiliary pragma can be used to describe a function that doesn't return to the caller:

    #pragma aux sym aborts [;]

where

sym
is a function name.

Consider the following example:

#pragma aux exitrtn aborts;
extern void exitrtn(void);

void rtn()
  {
    exitrtn();
  }

The function exitrtn() is defined to be a function that doesn't return. For example, it may call exit() to return to the system. In this case, Watcom C/C++ generates a jmp instruction instead of a call instruction to invoke exitrtn().

Describing how functions use memory

The following form of the auxiliary pragma can be used to describe a function that doesn't modify any memory (that is, global or static variables) that is used directly or indirectly by the caller.

    #pragma aux sym modify nomemory [;]

where

sym
is a function name.

Consider the following example:

#pragma off (check_stack);

extern void myrtn(void);

int i = { 1033 };

extern Rtn() {
    while( i < 10000 ) {
    i += 383;
    }
    myrtn();
    i += 13143;
};

To compile the above program, rtn.c, we issue the appropriate one of the following commands:

wcc rtn -oai -d1
wpp rtn -oai -d1
wcc386 rtn -oai -d1
wpp386 rtn -oai -d1

For illustrative purposes, we omit loop optimizations from the list of code optimizations that we want the compiler to perform. The d1 compiler option is specified so that the object file produced by Watcom C/C++ contains source line information.

We can generate a file containing a disassembly of rtn.o by issuing the following command.

wdisasm rtn -l -s -r

The s option is specified so that the listing file produced by the Watcom Disassembler contains source lines taken from rtn.c. The listing file rtn.lst appears as follows:


Module: rtn.c
Group: 'DGROUP' CONST,_DATA

Segment: '_TEXT' BYTE  0026 bytes

#pragma off (check_stack);

extern void MyRtn( void );

int i = { 1033 };

extern Rtn()
  {
 0000  52                   Rtn_         push    DX
 0001  8b 16 00 00                       mov     DX,_i

    while( i < 10000 ) {
 0005  81 fa 10 27          L1           cmp     DX,2710H
 0009  7d 06                             jge     L2

    i += 383;
    }
 000b  81 c2 7f 01                       add     DX,017fH
 000f  eb f4                             jmp     L1

    MyRtn();
 0011  89 16 00 00          L2           mov     _i,DX
 0015  e8 00 00                          call    MyRtn_
 0018  8b 16 00 00                       mov     DX,_i

    i += 13143;
 001c  81 c2 57 33                       add     DX,3357H
 0020  89 16 00 00                       mov     _i,DX

  };
 0024  5a                                pop     DX
 0025  c3                                ret

No disassembly errors

------------------------------------------------------------

Segment: '_DATA' WORD  0002 bytes
 0000  09 04               _i           - ..

No disassembly errors

------------------------------------------------------------

Let's add the following auxiliary pragma to the source file:

    #pragma aux myrtn modify nomemory;

If we compile the source file with the above pragma, and disassemble the object file using the Watcom Disassembler, we get the following listing file:


Module: rtn.c
Group: 'DGROUP' CONST,_DATA

Segment: '_TEXT' BYTE  0022 bytes

#pragma off (check_stack);

extern void MyRtn( void );
#pragma aux MyRtn modify nomemory;

int i = { 1033 };

extern Rtn()
  {
 0000  52                   Rtn_         push    DX
 0001  8b 16 00 00                       mov     DX,_i

    while( i < 10000 ) {
 0005  81 fa 10 27          L1           cmp     DX,2710H
 0009  7d 06                             jge     L2

    i += 383;
    }
 000b  81 c2 7f 01                       add     DX,017fH
 000f  eb f4                             jmp     L1

    MyRtn();
 0011  89 16 00 00          L2           mov     _i,DX
 0015  e8 00 00                          call    MyRtn_

    i += 13143;
 0018  81 c2 57 33                       add     DX,3357H
 001c  89 16 00 00                       mov     _i,DX

  };
 0020  5a                                pop     DX
 0021  c3                                ret

No disassembly errors

------------------------------------------------------------

Segment: '_DATA' WORD  0002 bytes
 0000  09 04               _i           - ..

No disassembly errors

------------------------------------------------------------

Notice that the value of i is in register DX after completion of the while loop. After the call to myrtn(), the value of i isn't loaded from memory into a register to perform the final addition. The auxiliary pragma informs the compiler that myrtn() doesn't modify any memory (that is, global or static variables) that is used directly or indirectly by Rtn(), and hence register DX contains the correct value of i.

The preceding auxiliary pragma deals with routines that modify memory. Let's consider the case where routines reference memory. The following form of the auxiliary pragma can be used to describe a function that doesn't reference any memory (that is, global or static variables) that is used directly or indirectly by the caller:

    #pragma aux sym parm nomemory modify nomemory [;]

where

sym
is a function name.
You must specify both parm nomemory and modify nomemory.

Let's replace the auxiliary pragma in the above example with the following auxiliary pragma:

    #pragma aux myrtn parm nomemory modify nomemory;

If you now compile our source file and disassemble the object file using wdisasm, the result is the following listing file:


Module: rtn.c
Group: 'DGROUP' CONST,_DATA

Segment: '_TEXT' BYTE  001e bytes

#pragma off (check_stack);

extern void MyRtn( void );
#pragma aux MyRtn parm nomemory modify nomemory;

int i = { 1033 };

extern Rtn()
  {
 0000  52                   Rtn_         push    DX
 0001  8b 16 00 00                       mov     DX,_i

    while( i < 10000 ) {
 0005  81 fa 10 27          L1           cmp     DX,2710H
 0009  7d 06                             jge     L2

    i += 383;
    }
 000b  81 c2 7f 01                       add     DX,017fH
 000f  eb f4                             jmp     L1

    MyRtn();
 0011  e8 00 00             L2           call    MyRtn_

    i += 13143;
 0014  81 c2 57 33                       add     DX,3357H
 0018  89 16 00 00                       mov     _i,DX

  };
 001c  5a                                pop     DX
 001d  c3                                ret

No disassembly errors

------------------------------------------------------------

Segment: '_DATA' WORD  0002 bytes
 0000  09 04               _i           - ..

No disassembly errors

------------------------------------------------------------

Notice that after completion of the while loop we didn't have to update i with the value in register DX before calling myrtn(). The auxiliary pragma informs the compiler that myrtn() doesn't reference any memory (that is, global or static variables) that is used directly or indirectly by myrtn(), so updating i isn't necessary before calling myrtn().

Describing the registers modified by a function

The following form of the auxiliary pragma can be used to describe the registers that a function will use without saving:

    #pragma aux sym modify [exact] reg_set [;]

where

sym
is a function name.
reg_set
is a register set.

Specifying a register set informs Watcom C/C++ that the registers belonging to the register set are modified by the function. That is, the value in a register before calling the function is different from its value after execution of the function.

Registers that are used to pass arguments are assumed to be modified, and hence don't have to be saved and restored by the called function. If necessary, the caller will contain code to save and restore the contents of registers used to pass arguments. Note that saving and restoring the contents of these registers might not be necessary if the called function doesn't modify them. The following form of the auxiliary pragma can be used to describe exactly those registers that are modified by the called function:

    #pragma aux sym modify exact reg_set [;]

where

sym
is a function name.
reg_set
is a register set.

The above form of the auxiliary pragma tells Watcom C/C++ not to assume that the registers used to pass arguments are modified by the called function. Instead, only the registers specified in the register set are modified. This prevents the generation of the code that unnecessarily saves and restores the contents of the registers used to pass arguments.

An example

As mentioned in an earlier section, the following pragma defines the calling convention for functions compiled by Microsoft C.

#pragma aux MS_C "_*"                       \
         parm caller []                \
         value struct float struct routine [ax]\
         modify [ax bx cx dx es];

Let's discuss this pragma in detail.

"_*"
specifies that all function and variable names are preceded by the underscore character (_) when translated from source form to object form.
parm caller []
specifies that all arguments are to be passed on the stack (an empty register set is specified), and the caller will remove the arguments from the stack.
value struct
marks the section describing how the called routine returns structure information:
float
specifies that floating-point arguments are returned in the same way as structures are returned.
struct
specifies that 1, 2 and 4-byte structures aren't to be returned in registers.
routine
specifies that the called routine allocates storage for the return structure, and returns with a register pointing at it.
[ax]
specifies that register AX is used to point to the structure return value.
modify [ax bx cx dx es]
specifies that registers AX, BX, CX, DX and ES aren't preserved by the called routine.

Note that the default method of returning integer values is used; 1-byte characters are returned in register AL, 2-byte integers are returned in register AX, and 4-byte integers are returned in the register pair DX:AX.

Auxiliary pragmas and the 80x87

This section deals with those aspects of auxiliary pragmas that are specific to the 80x87. The discussion in this chapter assumes that one of the fpi or fpi87 options is used to compile functions. The following areas are affected by the use of these options:

Using the 80x87 to pass arguments

By default, floating-point arguments are passed on the 80x86 stack. The 80x86 registers are never used to pass floating-point arguments when a function is compiled with the fpi or fpi87 option. However, they can be used to pass arguments whose type isn't floating-point, such as arguments of type int.

The following form of the auxiliary pragma can be used to describe the registers that are to be used to pass arguments to functions:

    #pragma aux sym parm {reg_set} [;]

where

sym
is a function name.
reg_set
is a register set. The register set can contain 80x86 registers and/or the string ``8087''.
If an empty register set is specified, all arguments, including floating-point arguments, are passed on the 80x86 stack.

When the string ``8087'' appears in a register set, it simply means that floating-point arguments can be passed in 80x87 floating-point registers if the source file is compiled with the fpi or fpi87 option. Before discussing argument passing in detail, some general notes on the use of the 80x87 floating-point registers are given.

The 80x87 contains 8 floating-point registers that essentially form a stack. The stack pointer is called ST, and is a number between 0 and 7, identifying which 80x87 floating-point register is at the top of the stack. ST is initially 0. 80x87 instructions refer to these registers by specifying a floating-point register number. This number is then added to the current value of ST. The sum (taken modulo 8) specifies the 80x87 floating-point register to be used. The notation ST(n), where n is between 0 and 7, is used to refer to the position of an 80x87 floating-point register relative to ST.

When a floating-point value is loaded onto the 80x87 floating-point register stack, ST is decremented (modulo 8), and the value is loaded into ST(0). When a floating-point value is stored and popped from the 80x87 floating-point register stack, ST is incremented (modulo 8) and ST(1) becomes ST(0).

The following illustrates the use of the 80x87 floating-point registers as a stack, assuming that the value of ST is 4 (4 values have been loaded onto the 80x87 floating-point register stack).

fig: ./images/8087stk.gif

Starting with version 9.5, the Watcom compilers use all eight of the 80x87 registers as a stack. The initial state of the 80x87 register stack is empty before a program begins execution.

For compatibility with code compiled with version 9.0 and earlier, you can compile with the fpr option. In this case only four of the eight 80x87 registers are used as a stack. These four registers are used to pass arguments. The other four registers form what is called the 80x87 cache. The cache is used for local floating-point variables. The state of the 80x87 registers before a program began execution is as follows:

  1. The four 80x87 floating-point registers that form the stack are uninitialized.
  2. The four 80x87 floating-point registers that form the 80x87 cache are initialized with zero.

Hence, initially the 80x87 cache consists of ST(0), ST(1), ST(2) and ST(3). ST has the value 4, as in the above diagram. When a floating-point value is pushed on the stack (as is the case when passing floating-point arguments), it becomes ST(0), and the 80x87 cache consists of ST(1), ST(2), ST(3) and ST(4). When the 80x87 stack is full, ST(0), ST(1), ST(2) and ST(3) form the stack, and ST(4), ST(5), ST(6) and ST(7) form the 80x87 cache. Version 9.5 and later no longer use this strategy.

The rules for passing arguments are as follows:

  1. If the argument isn't floating-point, use the procedure described earlier in this chapter.
  2. If the argument is floating-point, and a previous argument has been assigned a position on the 80x86 stack (instead of the 80x87 stack), the floating-point argument is also assigned a position on the 80x86 stack. Otherwise proceed to the next step.
  3. If the string ``8087'' appears in a register set in the pragma, and if the 80x87 stack isn't full, the floating-point argument is assigned floating-point register ST(0) (the top element of the 80x87 stack). The previous top element (if there is one) is now in ST(1). Since arguments are pushed on the stack from right to left, the leftmost floating-point argument is in ST(0). Otherwise the floating-point argument is assigned a position on the 80x86 stack.

Consider the following example:

#pragma aux myrtn parm [8087];

void main()
{
    float    x;
    double   y;
    int      i;
    long int j;

    x = 7.7;
    i = 7;
    y = 77.77;
    j = 77;
    myrtn( x, i, y, j );
}

The function myrtn() is an assembly language function that requires four arguments:

  • The first argument is of type float (4 bytes)
  • the second argument is of type int (2 bytes)
  • the third argument is of type double (8 bytes)
  • the fourth argument is of type long int (4 bytes)

These arguments are passed to myrtn() in the following way:

  1. Since ``8087'' is specified in the register set, the first argument, being of type float, is passed in an 80x87 floating-point register.
  2. The second argument is passed on the stack, since no 80x86 registers are specified in the register set.
  3. The third argument is also passed on the stack. Remember the following rule: once an argument is assigned a position on the stack, all remaining arguments are assigned a position on the stack. Note that the above rule holds even though there are some 80x87 floating-point registers available for passing floating-point arguments.
  4. The fourth argument is also passed on the stack.

Let's change the auxiliary pragma in the above example as follows:

    #pragma aux myrtn parm [ax 8087];

The arguments are now passed to myrtn() in the following way:

  1. Since ``8087'' is specified in the register set, the first argument, being of type float, is passed in an 80x87 floating-point register.
  2. The second argument is passed in register AX, exhausting the set of available 80x86 registers for argument passing.
  3. The third argument, being of type double, is also passed in an 80x87 floating-point register.
  4. The fourth argument is passed on the stack, since no 80x86 registers remain in the register set.

Using the 80x87 to return function values

The following form of the auxiliary pragma can be used to describe a functionthat returns a floating-point value in ST(0):

    #pragma aux sym value reg_set [;]

where

sym
is a function name.
reg_set
is a register set containing ``8087'' (that is, [8087])

Preserving 80x87 floating-point registers across calls

The code generator assumes that all eight 80x87 floating-point registers are available for use within a function unless the fpr option is used to generate backward-compatible code (older Watcom compilers used four registers as a cache). The following form of the auxiliary pragma specifies that the floating-point registers in the 80x87 cache may be modified by the specified function:

    #pragma aux sym modify reg_set [;]

where

sym
is a function name.
reg_set
is a register set containing the string ``8087''.

This instructs Watcom C/C++ to save any local variables that are located in the 80x87 cache before calling the specified routine.