This chapter discusses 32-bit pragmas:
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:
Currently, the following options can be specified with pragmas:
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);
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");
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. |
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
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. |
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
#define seg_name "MY_CODE_SEG" #pragma code_seg ( seg_name );
#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.
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
The lib form of this pragma offers the same features as the library pragma. See "Using pragmas to specify default libraries" for more information.
Consider the following example:
#pragma comment ( lib, "mylib" );
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
#define seg_name "MY_DATA_SEG" #pragma data_seg ( seg_name );
#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.
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
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.
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
This pragma is designed to be used for information purposes 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
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.
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
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 |
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
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 ) ); }
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:
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.
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
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
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
The default value for n is 8; the maximum is 255.
The expansion of recursive in-line functions is controlled by the inline_recursion pragma. |
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.
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
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 ) ); }
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
For example,
#if defined(__386__) ... #else #pragma message ( "assuming 16-bit compile" ); #endif
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:
This form of the pack pragma can be used to change the alignment of structures and their fields in memory.
#pragma pack ( n ) [;]
where
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 |
---|---|---|---|---|
1 | 0 | 0 | 0 | 0 |
2 | 0 | 2 | 2 | 2 |
4 | 0 | 2 | 4 | 4 |
8 | 0 | 2 | 4 | 8 |
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.
This form of the pack pragma can be used to save the current alignment amount on an internal stack:
#pragma pack ( push ) [;]
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 ) [;]
This form of the pack pragma can be used to restore the previous alignment amount from an internal stack:
#pragma pack ( pop ) [;]
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
The warning pragma sets the level of warning messages. The form of the warning pragma is as follows:
#pragma warning msg_num level [;]
where
Make sure to strip all leading zeroes from the message number to avoid interpretation as an octal constant. |
The following sections describe the capabilities provided by auxiliary pragmas:
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:
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:
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:
The simple form of the auxiliary pragma used to specify an alias is as follows:
#pragma aux ( sym, [far16] alias ) [;]
where
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.
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.
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
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.
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. |
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:
#pragma aux __cdecl "_*" \ parm caller [ ] \ value struct float struct routine [eax] \ modify [eax ecx edx]
Note the following:
#pragma aux __pascal "^" \ parm reverse routine [ ] \ value struct float struct caller [ ] \ modify [eax ebx ecx edx]
Note the following:
#pragma aux __stdcall "_*@nnn" \ parm routine [ ] \ value struct struct caller [ ] \ modify [eax ecx edx]
Note the following:
#pragma aux __syscall "*" \ parm caller [ ] \ value struct struct caller [ ] \ modify [eax ecx edx]
Note the following:
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
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 "^";
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
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 ];
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 */;
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
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
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
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
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
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
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:
[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.
[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.
[EBP]
the argument is pushed on the stack, since a valid register combination for 4-byte arguments isn't contained in the register set.
Note the following:
Register pair | Equivalent register |
---|---|
AH and AL | AX |
DH and DL | DX |
CH and CL | CX |
BH and BL | BX |
Specifying certain registers implies that another register has been specified, as shown below.
Register | Implied register |
---|---|
EAX | AX |
EBX | BX |
ECX | CX |
EDX | DX |
EDI | DI |
ESI | SI |
EBP | BP |
ESP | SP |
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.
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.
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:
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.
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:
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:
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
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.
The following form of the auxiliary pragma specifies that arguments are passed in the reverse order:
#pragma aux sym parm reverse [;]
where
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 [ ];
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
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
Depending on the type of the return value, only certain registers are allowed in reg_set:
Note the following:
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
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:
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
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
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
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
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
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().
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
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
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().
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
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
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.
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:
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.
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:
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
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).
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:
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:
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:
These arguments are passed to myrtn() in the following way:
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:
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
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
This instructs Watcom C/C++ to save any local variables that are located in the 80x87 cache before calling the specified routine.