This chapter discusses 16-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 run-time call to a stack-checking routine at the start of every routine compiled. This run-time routine issues an error if a stack overflow occurs when invoking the routine. The default is to check for stack overflows. Stack-overflow checking is particularly useful when functions are invoked recursively. Note that if the stack overflows, and stack checking has been suppressed, unpredictable results can occur.
If a stack overflow does occur during execution and you're sure that your program isn't in error (that is, it isn't unnecessarily recursing), you must increase the stack size. This is done by linking your application again and specifying the STACK option to the Watcom Linker with a larger stack size. See the section `` The STACK Option'' in the Watcom Linker chapter.
It's also possible to specify more than one option in a pragma, as illustrated by the following example:
#pragma on (check_stack unreferenced);
Default libraries are specified in special object module records. Library names are extracted from these special records by the Watcom Linker. When unresolved references remain after processing all object modules specified in linker FILE directives, these default libraries are searched after all libraries specified in linker LIBRARY directives have been searched.
By default, that is if no library pragma is specified, the Watcom C/C++ compiler generates default libraries corresponding to the memory model and floating-point model used to compile the file, and puts them in the object file defining the main program. For example, if you have compiled the source file containing the main program for the medium memory model and the floating-point calls floating-point model, the libraries clibm and mathm are placed in the object file.
If you wish to add your own default libraries to this list, you can do so with a library pragma. Consider the following example:
#pragma library (mylib);
The name mylib is added to the list of default libraries specified in the object file.
If the library specification contains characters such as `/', `:' or `,' (that is, any character not allowed in a C identifier), you must enclose it in double quotes, as in the following example:
#pragma library ("/usr/lib/graph.lib");
If you wish to specify more than one library in a library pragma, you must separate them with spaces, as in the following example:
#pragma library (mylib "/usr/lib/graph.lib");
The alloc_text pragma can be used to specify the name of the text segment into which the generated code for a function or a list of functions is to be placed. The following describes the form of the alloc_text pragma:
#pragma alloc_text ( seg_name, fn {, fn} ) [;]
where
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.
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 the section entitled ``Using Pragmas to Specify Default Libraries'' for more information.
For example,
#pragma comment ( lib, "mylib" );
The data_seg pragma can be used to specify the name of the segment into which data is to be placed. The form of the data_seg pragma is as follows:
#pragma data_seg ( seg_name [, class_name] ) [;]
where
#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 variables i and j are 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
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
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
For example,
#if defined(__386__) ... #elseif defined(__86__) ... #else #pragma error ( "neither __386__ or __86__ defined" ); #endif
Certain functions, listed in the description of the oi option, have intrinsic forms. These functions are special functions that are recognized by the compiler and processed in a special way. For example, the compiler might choose to generate in-line code for the function. The intrinsic attribute for these special functions is set by specifying the oi option, or by using an intrinsic pragma. The function pragma can be used to remove the intrinsic attribute for a specified list of functions.
The form of the function pragma is as follows:
#pragma function ( fn {, fn} ) [;]
where
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 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++ run-time 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 will take place before initialization of all other static data in the program, since a priority of 63 is assigned.
#pragma initialize before program
If you specify after instead of before, the initialization of the static data in the file will occur after initialization of all other static data in the program, since a priority of 65 is assigned.
Note that the following is equivalent to the before example
#pragma initialize 63
and the following is equivalent to the after example.
#pragma initialize 65
You should use a priority of 32 (the priority used when the library keyword is specified) when developing class libraries. This ensures that static data defined by the class library is initialized before static data defined by the program. The following initialize pragma can be used to do this:
#pragma initialize library
When an in-line function is called, the function call may be replaced by the in-line expansion for that function. This in-line expansion might include calls to other in-line functions, which can also be expanded. The inline_depth pragma can be used to set the number of times this expansion of in-line functions will occur for a call.
The form of the inline_depth pragma is as follows:
#pragma inline_depth [(] n [)] [;]
where
The default value for n is 8; the maximum is 255.
The inline_recursion pragma controls the recursive expansion of in-line functions. The form of the inline_recursion pragma is as follows:
#pragma inline_recursion [(] on [)] [;]
or
#pragma inline_recursion [(] off [)] [;]
Specifying on enables the expansion of recursive in-line functions. The depth of expansion is specified by the inline_depth pragma. The default depth is 8.
Specifying off suppresses the expansion of recursive in-line functions. This is the default.
Certain functions, listed in the description of the oi option, have intrinsic forms. These functions are special functions that are recognized by the compiler and processed in a special way. For example, the compiler may choose to generate in-line code for the function. The intrinsic attribute for these special functions is set by specifying the oi option, or by using an intrinsic pragma.
The following describes the form of the intrinsic pragma:
#pragma intrinsic ( fn {, fn} ) [;]
where
Suppose the following source code is compiled without using the oi option, so that no function has the intrinsic attribute. If we want the intrinsic form of the sin() function to be used, we could specify the function in an intrinsic pragma, as follows:
#include <math.h> #pragma intrinsic( sin ); double test( double x ) { return( sin( x ) ); }
The message pragma can be used to issue a message with the specified text to the standard output, without terminating compilation. The following describes the form of the message pragma.
#pragma message ( "message text" ) [;]
where
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, as follows:
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 template classes. These templates can use other templates in their definitions, and so on. This pragma can be used to limit the depth of such nestings.
The form of the template_depth pragma is as follows:
#pragma template_depth [(] n [)] [;]
where
The default value for n is 8; the maximum is 255.
The warning pragma sets the level of warning messages. The form of the warning pragma is as follows:
#pragma warning msg_num level [;]
where
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, alias ) [;]
where
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 MS_C "_*" \ parm caller [] \ value struct float struct routine [ax] \ modify [ax bx cx dx es]; #pragma aux (MS_C) rtn1; #pragma aux (MS_C) rtn2; #pragma aux (MS_C) rtn3;
The routines rtn1(), rtn2() and rtn3() assume the same attributes as the alias name MS_C, which defines the calling convention used by the Microsoft C compiler. Whenever calls are made to rtn1(), rtn2() and rtn3(), the Microsoft C calling convention is used.
Note that if the attributes of MS_C change, only one pragma needs to be changed. If we hadn't used an alias name, and specified the attributes in each of the three pragmas for rtn1(), rtn2() and rtn3(), we would have to change all three pragmas. This approach also reduces the amount of memory required by the compiler to process the source file.
A number of symbols are predefined by the compiler with a set of attributes that describe a particular calling convention. These symbols can be used as aliases. The following is a list of these symbols.
The following sections describe the attributes of the above alias names:
#pragma aux __cdecl "_*" \ parm caller [] \ value struct float struct routine [ax] \ modify [ax bx cx dx es]
Note the following:
#pragma aux __pascal "^" \ parm reverse routine [] \ value struct float struct caller [] \ modify [ax bx cx dx es]
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 the upper case version of sym.
In the following example, the name myrtn is replaced by myrtn_ in the object file.
#pragma aux myrtn "*_";
This is the default for all function names.
In the following example, the name myvar is replaced by _myvar in the object file.
#pragma aux myvar "_*";
This is the default for all variable names.
The default mapping for all symbols can also be changed as illustrated by the following example:
#pragma aux default "_*_";
The above auxiliary pragma specifies that all names are prefixed and suffixed by an underscore character (`_').
The `^' character also has a special meaning. Whenever it's encountered in obj_name, it's replaced by the upper-case version of sym.
In the following example, the name myrtn is replaced by MYRTN in the object file.
#pragma aux myrtn "^";
The following form of the auxiliary pragma can be used to describe the way a function is to be called:
#pragma aux sym far [;]
or
#pragma aux sym near [;]
or
#pragma aux sym = in_line [;]
The in-line variable is defined as:
{ const | (seg id) | (offset id) | (reloff id) | (float fpinst) | "asm" }
where
In the following example, Watcom C/C++ generates a far call to the function myrtn().
#pragma aux myrtn far;
In the following example, Watcom C/C++ generates a near call to the function myrtn().
#pragma aux myrtn near;
In the following DOS example, Watcom C/C++ generates the sequence of bytes following the ``='' character in the auxiliary pragma whenever a call to mode4() is encountered. mode4() is called an in-line function.
void mode4(void); #pragma aux mode4 = \ 0xb4 0x00 /* mov AH,0 */ \ 0xb0 0x04 /* mov AL,4 */ \ 0xcd 0x10 /* int 10H */ \ modify [ AH AL ];
The sequence in the above DOS example represents the following lines of assembly language instructions:
mov AH,0 ; select function "set mode" mov AL,4 ; specify mode (mode 4) int 10H ; BIOS video call
The above example demonstrates how to generate BIOS function calls in-line without writing an assembly language function and calling it from your C/C++ program. The C prototype for the function mode4() isn't necessary but is included so that we can take advantage of the argument type checking provided by Watcom C/C++.
The following DOS example is equivalent to the above example, but mnemonics for the assembly language instructions are used instead of the binary encoding of the assembly language instructions.
void mode4(void); #pragma aux mode4 = \ "mov AH,0", \ "mov AL,4", \ "int 10H" \ modify [ AH AL ];
If a sequence of in-line assembly language instructions contains 80x87 floating-point instructions, each floating-point instruction must be preceded by float. Note that this is only required if you have specified the fpi compiler option; otherwise it's ignored.
The following example generates the 80x87 square-root instruction.
double mysqrt(double); #pragma aux mysqrt parm [8087] = \ float 0xd9 0xfa /* fsqrt */;
A sequence of in-line assembly language instructions may contain symbolic references. In the following example, a near call to the function myalias() is made whenever myrtn() is called.
extern void myalias(void); void myrtn(void); #pragma aux myrtn = \ 0xe8 offset myalias /* near call */;
In the following example, a far call to the function myalias() is made whenever myrtn() is called.
extern void myalias(void); void myrtn(void); #pragma aux myrtn = \ 0x9a offset myalias seg myalias /* far call */;
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
When compiling a Microsoft Windows application, you must use the zW option so that special prologue/epilogue sequences are generated. Furthermore, callback functions require larger prologue/epilogue sequences than those generated when the zW compiler option is specified. The following form of the auxiliary pragma causes a callback prologue/epilogue sequence to be generated for a callback function when compiled using the zW option.
#pragma aux sym export [;]
where
Alternatively, the zw compiler option can be used to generate callback prologue/epilogue sequences. However, all functions contained in a module compiled using the zw option will have a callback prologue/epilogue sequence, even if the functions aren't callback functions.
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:
[AX BX SI DI]
the argument is pushed on the stack, since a valid register combination for 8-byte arguments isn't contained in the register set.
[ES BP]
the argument is pushed on the stack, since a valid register combination for a far pointer isn't contained in the register set.
[ES DI]
the argument is pushed on the stack, since a valid register combination for 4-byte arguments isn't contained in the register set.
[BP]
the argument is pushed on the stack, since a valid register combination for 2-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 |
Consider the following example:
#pragma aux myrtn parm [ax bx cx dx] [bp si];
Suppose myrtn() is a routine with 3 arguments, each of type long int.
It's possible for registers from the second register set to be used before registers from the first register set are used. Consider the following example:
#pragma aux myrtn parm [ax bx cx dx] [si di];
Suppose myrtn() is a routine with 3 arguments, the first of type int, and the second and third of type long int.
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 SI, the second argument to be passed in register DI, and the third argument to be passed in register CX, the following auxiliary pragma can be used.
void mycopy( char near *, char *, int ); #pragma aux mycopy parm [DI] [SI] [CX];
Note that you must be aware of the size of the arguments to ensure that the arguments get passed in the appropriate registers.
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 the register pair DX:AX, and the argument is of type short int, the argument is converted to long int before assigning it to the register pair DX:AX.
In general, Watcom C/C++ assigns the following types to register sets:
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 will pop the arguments from the stack; routine specifies that the called routine will pop the arguments from the stack. If caller or routine is omitted, routine is assumed, unless the default has been changed in a previous auxiliary pragma, in which case the new default is assumed.
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 SI to point to it. The called routine then places the return value at the location pointed to by register SI.
The following form of the auxiliary pragma can be used to specify the register that is to be used to point to the return value:
#pragma aux sym value struct (caller|routine) reg_set [;]
where
The caller keyword specifies that the caller will allocate memory for the return value. The address of the memory allocated for the return value is placed in the register specified in the register set by the caller before the function is called. If an empty register set is specified, the address of the memory allocated for the return value is pushed on the stack immediately before the call and is returned in register AX by the called routine. It's assumed that the memory for the return value is allocated from the stack segment (the stack segment is contained in segment register SS).
The routine keyword specifies that the called routine will allocate memory for the return value. Upon returning to the caller, the register specified in the register set will contain the address of the return value. An empty register set isn't allowed.
Only the following registers are allowed in the register set: AX, DX, BX, CX, SI or DI. Note that in a big data model, the address in the return register is assumed to be in the segment specified by the value in the SS segment register.
If the size of the structure being returned is 1, 2 or 4 bytes, it's returned in registers. The return register is selected from the register set in the following way:
The following form of the auxiliary pragma can be used to specify that structures whose size is 1, 2 or 4 bytes aren't to be returned in registers. Instead, the caller allocates space on the stack for the structure return value and points register SI to it.
#pragma aux sym value struct struct [;]
where
The following form of the auxiliary pragma can be used to specify that function return values of type float or double aren't to be returned in registers. Instead, the caller allocates space on the stack for the return value and points register SI to it:
#pragma aux sym value struct float [;]
where
In other words, floating-point values are to be returned in the same way structures are returned.
The following form of the auxiliary pragma can be used to specify that function return values of type float or double aren't to be returned in 80x87 registers when compiling with the fpi or fpi87 option. Instead, the value is returned in 80x86 registers. Function return values of type float are returned in registers DX:AX. Function return values of type double are returned in registers AX:BX:CX:DX.
#pragma aux sym value no8087 [;]
where
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 0026 bytes #pragma off (check_stack); extern void MyRtn( void ); int i = { 1033 }; extern Rtn() { 0000 52 Rtn_ push DX 0001 8b 16 00 00 mov DX,_i while( i < 10000 ) { 0005 81 fa 10 27 L1 cmp DX,2710H 0009 7d 06 jge L2 i += 383; } 000b 81 c2 7f 01 add DX,017fH 000f eb f4 jmp L1 MyRtn(); 0011 89 16 00 00 L2 mov _i,DX 0015 e8 00 00 call MyRtn_ 0018 8b 16 00 00 mov DX,_i i += 13143; 001c 81 c2 57 33 add DX,3357H 0020 89 16 00 00 mov _i,DX }; 0024 5a pop DX 0025 c3 ret No disassembly errors ------------------------------------------------------------ Segment: '_DATA' WORD 0002 bytes 0000 09 04 _i - .. No disassembly errors ------------------------------------------------------------
Let's add the following auxiliary pragma to the source file:
#pragma aux myrtn modify nomemory;
If we compile the source file with the above pragma, and disassemble the object file using the Watcom Disassembler, we get the following listing file:
Module: rtn.c Group: 'DGROUP' CONST,_DATA Segment: '_TEXT' BYTE 0022 bytes #pragma off (check_stack); extern void MyRtn( void ); #pragma aux MyRtn modify nomemory; int i = { 1033 }; extern Rtn() { 0000 52 Rtn_ push DX 0001 8b 16 00 00 mov DX,_i while( i < 10000 ) { 0005 81 fa 10 27 L1 cmp DX,2710H 0009 7d 06 jge L2 i += 383; } 000b 81 c2 7f 01 add DX,017fH 000f eb f4 jmp L1 MyRtn(); 0011 89 16 00 00 L2 mov _i,DX 0015 e8 00 00 call MyRtn_ i += 13143; 0018 81 c2 57 33 add DX,3357H 001c 89 16 00 00 mov _i,DX }; 0020 5a pop DX 0021 c3 ret No disassembly errors ------------------------------------------------------------ Segment: '_DATA' WORD 0002 bytes 0000 09 04 _i - .. No disassembly errors ------------------------------------------------------------
Notice that the value of i is in register DX after completion of the while loop. After the call to myrtn(), the value of i isn't loaded from memory into a register to perform the final addition. The auxiliary pragma informs the compiler that myrtn() doesn't modify any memory (that is, global or static variables) that is used directly or indirectly by Rtn(), and hence register DX contains the correct value of i.
The preceding auxiliary pragma deals with routines that modify memory. Let's consider the case where routines reference memory. The following form of the auxiliary pragma can be used to describe a function that doesn't reference any memory (that is, global or static variables) that is used directly or indirectly by the caller:
#pragma aux sym parm nomemory modify nomemory [;]
where
Let's replace the auxiliary pragma in the above example with the following auxiliary pragma:
#pragma aux myrtn parm nomemory modify nomemory;
If you now compile our source file and disassemble the object file using wdisasm, the result is the following listing file:
Module: rtn.c Group: 'DGROUP' CONST,_DATA Segment: '_TEXT' BYTE 001e bytes #pragma off (check_stack); extern void MyRtn( void ); #pragma aux MyRtn parm nomemory modify nomemory; int i = { 1033 }; extern Rtn() { 0000 52 Rtn_ push DX 0001 8b 16 00 00 mov DX,_i while( i < 10000 ) { 0005 81 fa 10 27 L1 cmp DX,2710H 0009 7d 06 jge L2 i += 383; } 000b 81 c2 7f 01 add DX,017fH 000f eb f4 jmp L1 MyRtn(); 0011 e8 00 00 L2 call MyRtn_ i += 13143; 0014 81 c2 57 33 add DX,3357H 0018 89 16 00 00 mov _i,DX }; 001c 5a pop DX 001d c3 ret No disassembly errors ------------------------------------------------------------ Segment: '_DATA' WORD 0002 bytes 0000 09 04 _i - .. No disassembly errors ------------------------------------------------------------
Notice that after completion of the while loop we didn't have to update i with the value in register DX before calling myrtn(). The auxiliary pragma informs the compiler that myrtn() doesn't reference any memory (that is, global or static variables) that is used directly or indirectly by myrtn(), so updating i isn't necessary before calling myrtn().
The following form of the auxiliary pragma can be used to describe the registers that a function will use without saving:
#pragma aux sym modify [exact] reg_set [;]
where
Specifying a register set informs Watcom C/C++ that the registers belonging to the register set are modified by the function. That is, the value in a register before calling the function is different from its value after execution of the function.
Registers that are used to pass arguments are assumed to be modified, and hence don't have to be saved and restored by the called function. If necessary, the caller will contain code to save and restore the contents of registers used to pass arguments. Note that saving and restoring the contents of these registers might not be necessary if the called function doesn't modify them. The following form of the auxiliary pragma can be used to describe exactly those registers that are modified by the called function:
#pragma aux sym modify exact reg_set [;]
where
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 Microsoft C.
#pragma aux MS_C "_*" \ parm caller [] \ value struct float struct routine [ax]\ modify [ax bx cx dx es];
Let's discuss this pragma in detail.
Note that the default method of returning integer values is used; 1-byte characters are returned in register AL, 2-byte integers are returned in register AX, and 4-byte integers are returned in the register pair DX:AX.
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
When the string ``8087'' appears in a register set, it simply means that floating-point arguments can be passed in 80x87 floating-point registers if the source file is compiled with the fpi or fpi87 option. Before discussing argument passing in detail, some general notes on the use of the 80x87 floating-point registers are given.
The 80x87 contains 8 floating-point registers that essentially form a stack. The stack pointer is called ST, and is a number between 0 and 7, identifying which 80x87 floating-point register is at the top of the stack. ST is initially 0. 80x87 instructions refer to these registers by specifying a floating-point register number. This number is then added to the current value of ST. The sum (taken modulo 8) specifies the 80x87 floating-point register to be used. The notation ST(n), where n is between 0 and 7, is used to refer to the position of an 80x87 floating-point register relative to ST.
When a floating-point value is loaded onto the 80x87 floating-point register stack, ST is decremented (modulo 8), and the value is loaded into ST(0). When a floating-point value is stored and popped from the 80x87 floating-point register stack, ST is incremented (modulo 8) and ST(1) becomes ST(0).
The following illustrates the use of the 80x87 floating-point registers as a stack, assuming that the value of ST is 4 (4 values have been loaded onto the 80x87 floating-point register stack).
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 [ax 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 functionthat 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.