32-bit Pragmas

This chapter discusses 32-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 runtime call to a stack-checking routine at the start of every routine compiled. This runtime 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 "STACK Option" in the 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, in the object file defining the main program, default libraries corresponding to the memory model and floating-point model used to compile the file. For example, if you have compiled the source file containing the main program for the flat memory model and the floating-point calls floating-point model, the libraries clib3r and math3r 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");

Note: Don't use the library pragma with QNX. You must specify all the libraries on the command line; otherwise your program might not work correctly with some QNX utilities.

ALLOC_TEXT pragma (C only)

The alloc_text pragma can be used to specify the name of the text segment in which the generated code for a function, or a list of functions, is to be placed. The form of the alloc_text pragma is as follows:

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


Note: 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 "Using pragmas to specify default libraries" for more information.

comment_string
is an optional string literal, enclosed in quotes, that provides additional information for some comment types.

Consider the following example:

    #pragma comment ( lib, "mylib" );

DATA_SEG pragma

The data_seg pragma can be used to specify the name of the segment in 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 data for i and j is 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.
Note: 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 that is 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.
Note: 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.

Note: You should use the ANSI #error directive rather than this pragma. This pragma is provided for compatibility with legacy code. The following is an 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 compiler oi option, have intrinsic forms. These functions are special ones 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 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 the 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 occurs.

Priorities in the range 0-20 are reserved for the C++ compiler. This is to ensure that proper initialization of the C++ runtime 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 takes 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 occurs 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

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

It's recommended that a priority of 32 (the priority used when the library keyword is specified) be used when developing class libraries. This ensures that initialization of static data defined by the class library takes place before initialization of static data defined by the program. The following initialize pragma can be used to achieve 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 may include calls to other in-line functions that can also be expanded. The inline_depth pragma can be used to limit the number of times this expansion of in-line functions occurs 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.


Note: The expansion of recursive in-line functions is controlled by 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 expansion of recursive in-line functions. This is the default.

INTRINSIC pragma

Certain functions, listed in the description of the compiler oi option, have intrinsic forms. These functions are special ones 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 form of the intrinsic pragma is as follows:

    #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 had the intrinsic attribute. If we wanted the intrinsic form of the sin() function to be used, we could specify the function in an intrinsic pragma.

#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 form of the message pragma is as follows:

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

where

message text
is the text of the message that you wish to display, enclosed by 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. 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 templates. 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 nesting is permitted. 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.
Note: 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, [far16] alias ) [;]

where

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

The far16 attribute should only be used on systems that permit the calling of 16-bit code from 32-bit code. Currently, the only supported operating system that allows this is 32-bit OS/2. If you have any libraries of functions or APIs that are only available as 16-bit code and you wish to access these functions and APIs from 32-bit code, you must specify the far16 attribute. If the far16 attribute is specified, the compiler generates special code that allows the 16-bit code to be called from 32-bit code.


Note: A far16 function must be a function whose attributes are those specified by one of the alias names __cdecl or __pascal. These alias names are described in a later section.

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 HIGH_C "*"                          \
                   parm caller [ ]        \
                   value no8087                 \
                   modify [eax ecx edx fs gs];
#pragma aux (HIGH_C) rtn1;
#pragma aux (HIGH_C) rtn2;
#pragma aux (HIGH_C) rtn3;

The routines rtn1(), rtn2() and rtn3() assume the same attributes as the alias name HIGH_C, which defines the calling convention used by the MetaWare High C compiler. Note that register ES must also be specified in the modify register set when using a memory model that isn't a small data model. Whenever calls are made to rtn1(), rtn2() and rtn3(), the MetaWare High C calling convention is used.

Note that if the attributes of HIGH_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.


Note: The alias name HIGH_C is just another symbol. If HIGH_C appears in your source code, it assumes the attributes specified in the pragma for HIGH_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:

__cdecl or cdecl
defines the calling convention used by Microsoft compilers.
__pascal or pascal
defines the calling convention used by OS/2 1.x and Windows 3.x API functions.
__stdcall or stdcall
defines a special calling convention used by the Win32 API functions.
__syscall or syscall
defines the calling convention used by the 32-bit OS/2 API functions.
__system and system
identical to __syscall.

Predefined __cdecl alias

    #pragma aux __cdecl "_*"                            \
                parm caller [ ]                          \
                value struct float struct routine [eax] \
                modify [eax ecx edx]

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 removes 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 EAX.
  4. Registers EAX, ECX and EDX 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 [eax ebx ecx edx]

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 removes 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 EAX contains the address of the space allocated for the return value.
  4. Registers EAX, EBX, ECX and EDX aren't saved and restored when a call is made.

Predefined __stdcall alias

#pragma aux __stdcall "_*@nnn"           \
           parm routine [ ]               \
           value struct struct caller [ ] \
           modify [eax ecx edx]

Note the following:

  1. All symbols are preceded by an underscore character.
  2. All C symbols (extern "C" symbols in C++) are suffixed by @nnn, where nnn is the sum of the argument sizes (each size is rounded up to a multiple of 4 bytes so that char and short are size 4). When the argument list contains "...", the @nnn suffix is omitted.
  3. Arguments are pushed on the stack from right to left. That is, the last argument is pushed first. The called routine removes the arguments from the stack.
  4. 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 EAX contains address of the space allocated for the return value. Floating-point values are returned in 80x87 register ST(0).
  5. Registers EAX, ECX and EDX aren't saved and restored when a call is made.

Predefined __syscall alias

#pragma aux __syscall "*"                \
           parm caller [ ]                \
           value struct struct caller [ ] \
           modify [eax ecx edx]

Note the following:

  1. Symbols names aren't modified, that is, they aren't adorned with leading or trailing underscores.
  2. Arguments are pushed on the stack from right to left. That is, the last argument is pushed first. The calling routine removes the arguments from the stack.
  3. 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 EAX contains address of the space allocated for the return value. Floating-point values are returned in 80x87 register ST(0).
  4. Registers EAX, ECX and EDX 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 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)
            | "asm" }

where

sym
is a function name
const
is a valid C/C++ integer constant.
id
is any valid C/C++ identifier.
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;

Note: 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;

Note: 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 ];

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.

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 one of the following register pairs: EDX:EAX, ECX:EBX, ECX:EAX, ECX:ESI, EDX:EBX, EDI:EAX, ECX:EDI, EDX:ESI, EDI:EBX, ESI:EAX, ECX:EDX, EDX:EDI, EDI:ESI, ESI:EBX or EBX:EAX. 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,

        [EBP EBX]
        
    

    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:EAX, CX:EBX, CX:EAX, CX:ESI, DX:EBX, DI:EAX, CX:EDI, DX:ESI, DI:EBX, SI:EAX, CX:EDX, DX:EDI, DI:ESI, SI:EBX, BX:EAX, FS:ECX, FS:EDX, FS:EDI, FS:ESI, FS:EBX, FS:EAX, GS:ECX, GS:EDX, GS:EDI, GS:ESI, GS:EBX, GS:EAX, DS:ECX, DS:EDX, DS:EDI, DS:ESI, DS:EBX, DS:EAX, ES:ECX, ES:EDX, ES:EDI, ES:ESI, ES:BX or ES:AX. For example, if a far pointer is passed to a function with the following register set,

        [ES EBP]
        
    

    the argument would be 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 int) are EAX, EBX, ECX, EDX, ESI and EDI. For example, if the following register set is specified for a routine with one argument of type int,

        [EBP]
        
    

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

  4. Arguments whose size is 1 byte or 2 bytes (arguments of type char and short int) are promoted to 4 bytes, and are then assigned registers as if they were 4-byte arguments.
  5. 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 [EAX EBX ECX EDX].
  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

    Specifying certain registers implies that another register has been specified, as shown below.

    RegisterImplied register
    EAXAX
    EBXBX
    ECXCX
    EDXDX
    EDIDI
    ESISI
    EBPBP
    ESPSP
  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.
  4. If you're compiling for the flat memory model, any register combination containing DS or ES becomes illegal. In a flat memory model, code and data reside in the same segment. Segment registers DS and ES point to this segment, and must remain unchanged.

Consider the following example:

    #pragma aux myrtn parm [eax ebx ecx edx] [ebp esi];

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

  1. The first argument is passed in the register pair EDX:EAX.
  2. The second argument is passed in the register pair ECX:EBX.
  3. The third argument is passed on the stack, since EBP:ESI isn't a valid register pair for arguments of type double.

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 [eax ebx ecx edx] [esi edi];

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

  1. The first argument is passed in the register EAX.
  2. The second argument is passed in the register pair ECX:EBX.
  3. The third argument is passed in the register set EDI:ESI.

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 [EAX EBX ECX EDX] 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 ESI, the second argument to be passed in register EDI, and the third argument to be passed in register ECX, the following auxiliary pragma can be used.

void mycopy( char near *, char *, int );
#pragma aux mycopy parm [EDI] [ESI] [ECX];

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 register EAX, and the argument is of type short int, the argument is converted to long int before assigning it to register EAX.

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 a single 32-bit register (4 bytes) is assigned a type of unsigned long int.
  • A register set consisting of two 32-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 pops the arguments from the stack; routine specifies that the called routine pops 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 (including near pointers), only the following registers are allowed: EAX, EDX, EBX, ECX, ESI and EDI. If no register set is specified, register EAX is used.
  • For functions that return far pointers, the following register pairs are allowed: DX:EAX, CX:EBX, CX:EAX, CX:ESI, DX:EBX, DI:EAX, CX:EDI, DX:ESI, DI:EBX, SI:EAX, CX:EDX, DX:EDI, DI:ESI, SI:EBX, BX:EAX, FS:ECX, FS:EDX, FS:EDI, FS:ESI, FS:EBX, FS:EAX, GS:ECX, GS:EDX, GS:EDI, GS:ESI, GS:EBX, GS:EAX, DS:ECX, DS:EDX, DS:EDI, DS:ESI, DS:EBX, DS:EAX, ES:ECX, ES:EDX, ES:EDI, ES:ESI, ES:EBX or ES:EAX. If no register set is specified, the registers DX:EAX are used.
  • For 8-byte return values (functions of type double), only the following register pairs are allowed: EDX:EAX, ECX:EBX, ECX:EAX, ECX:ESI, EDX:EBX, EDI:EAX, ECX:EDI, EDX:ESI, EDI:EBX, ESI:EAX, ECX:EDX, EDX:EDI, EDI:ESI, ESI:EBX or EBX:EAX. If no register set is specified, the registers EDX:EAX 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.
  3. If you're compiling for the flat memory model, any register combination containing DS or ES becomes illegal. In a flat memory model, code and data reside in the same segment. Segment registers DS and ES point to this segment, and must remain unchanged.

Returning structures

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

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 allocates 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 EAX by the called routine.

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

Only the following registers are allowed in the register set: EAX, EDX, EBX, ECX, ESI and EDI. 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 registers: EAX, EDX, EBX, ECX, ESI or EDI. If no register set is specified, register EAX 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 ESI 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 point register ESI 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 register EAX. Function return values of type double are returned in registers EDX:EAX.

    #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 USE32  00000036 bytes

#pragma off (check_stack);

extern void myrtn(void);

int i = { 1033 };

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

    while( i < 10000 ) {
 0007  81 fa 10 27 00 00 L1           cmp     EDX,00002710H
 000d  7d 08                          jge     L2

    i += 383;
    }
 000f  81 c2 7f 01 00 00              add     EDX,0000017fH
 0015  eb f0                          jmp     L1

    myrtn();
 0017  89 15 00 00 00 00 L2           mov     _i,EDX
 001d  e8 00 00 00 00                 call     myrtn_
 0022  8b 15 00 00 00 00              mov     EDX,_i

    i += 13143;
 0028  81 c2 57 33 00 00              add     EDX,00003357H
 002e  89 15 00 00 00 00              mov     _i,EDX

}
 0034  5a                             pop     EDX
 0035  c3                             ret

No disassembly errors

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

Segment: '_DATA' WORD USE32  00000004 bytes
 0000  09 04 00 00           _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 USE32  00000030 bytes

#pragma off (check_stack);
#pragma aux myrtn modify nomemory;

extern void myrtn(void);

int i = { 1033 };

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

    while( i < 10000 ) {
 0007  81 fa 10 27 00 00 L1           cmp     EDX,00002710H
 000d  7d 08                          jge     L2

    i += 383;
    }
 000f  81 c2 7f 01 00 00              add     EDX,0000017fH
 0015  eb f0                          jmp     L1

    myrtn();
 0017  89 15 00 00 00 00 L2           mov     _i,EDX
 001d  e8 00 00 00 00                 call     myrtn_

    i += 13143;
 0022  81 c2 57 33 00 00              add     EDX,00003357H
 0028  89 15 00 00 00 00              mov     _i,EDX

}
 002e  5a                             pop     EDX
 002f  c3                             ret

No disassembly errors

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

Segment: '_DATA' WORD USE32  00000004 bytes
 0000  09 04 00 00           _i           - ...

No disassembly errors

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

Notice that the value of i is in register EDX 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 EDX 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.

Note: 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 the 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 USE32  0000002a bytes

#pragma off (check_stack);
#pragma aux myrtn parm nomemory modify nomemory;

extern void myrtn(void);

int i = { 1033 };

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

    while( i < 10000 ) {
 0007  81 fa 10 27 00 00 L1           cmp     EDX,00002710H
 000d  7d 08                          jge     L2

    i += 383;
    }
 000f  81 c2 7f 01 00 00              add     EDX,0000017fH
 0015  eb f0                          jmp     L1

    myrtn();
 0017  e8 00 00 00 00     L2          call     myrtn_

    i += 13143;
 001c  81 c2 57 33 00 00              add     EDX,00003357H
 0022  89 15 00 00 00 00              mov     _i,EDX

}
 0028  5a                             pop     EDX
 0029  c3                             ret

No disassembly errors

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

Segment: '_DATA' WORD USE32  00000004 bytes
 0000  09 04 00 00           _i           - ...

No disassembly errors

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

Notice that after completion of the while loop we didn't have to update i with the value in register EDX 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 uses 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 contains code to save and restore the contents of registers used to pass arguments. Note that saving and restoring the contents of these registers may 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.

Example

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

#pragma aux HIGH_C "*"                       \
           parm caller [ ]                    \
           value no8087                      \
           modify [eax ecx edx fs gs];

Note that register ES must also be specified in the modify register set when using a memory model with a non-small data model. Let's discuss this pragma in detail:

"*"
specifies that all function and variable names appear in object form as they do in source form.
parm caller [ ]
specifies that all arguments are to be passed on the stack (an empty register set is specified) and the caller removes the arguments from the stack.
value no8087
specifies that floating-point values are to be returned using 80x86 registers and not 80x87 floating-point registers.
modify [eax ecx edx fs gs]
specifies that registers EAX, ECX, EDX, FS and GS 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 register EAX.

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

Note: 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 (inclusive), 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.


Note:

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:

  • The four 80x87 floating-point registers that form the stack are uninitialized.
  • 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 is of type float (4 bytes)
  • the second is of type int (4 bytes)
  • the third is of type double (8 bytes)
  • the fourth 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 were 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 [eax 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 EAX, 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 function that 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.