Chapter 1. Understanding the Silicon Graphics C++ Environment

This chapter describes the Silicon Graphics C++ compiler environment and contains the following major sections:

Silicon Graphics C++ Environment

The Silicon Graphics 7.3 C++ environment provides three C++ compilers for IRIX 6.x systems. As shown in Figure 1-1, there are two MIPSpro C++ compilers, a 32-bit version and a 64-bit version. They are known as the N32 and 64 compilers because of the application binary interfaces (ABIs) they generate. These compilers accept a dialect of C++ that closely resembles the ANSI/ISO draft C++ standard and are strongly recommended by Silicon Graphics.

The 32-bit ucode C++ compiler (O32 ABI) is also available. The O32 compiler is an older compiler that is no longer being enhanced. It is included to support legacy code. The C++ compiler based on Cfront is still available but is no longer supported. See the C++ release notes for the latest information about the status of these compilers.

Figure 1-1. Silicon Graphics C++ Environment

Silicon Graphics C++ Environment

The following commands invoke the three compilers:

CC -n32 

32-bit native MIPSpro compiler. Generates N32 ABI objects.

CC -64 

64-bit native MIPSpro compiler. Generates 64 ABI objects.

CC -o32 or CC -32 

32-bit native ucode compiler. Generates O32 ABI objects.

For more information about compilers and ABIs, see the cc(1), CC(1), and ABI(5) man pages, or the MIPSpro Compiling and Performance Tuning Guide.

Understanding ABIs and ISAs

An application binary interface (ABI) defines a system interface for executing compiled programs. Among the important features the ABI specifies are the following:

  • Supported instruction set architectures (ISAs)

  • Size of the address space

  • Object file formats

  • Calling conventions

  • Register size

  • Number of registers

The three ABIs that are relevant to MIPSpro 7.3 C++ are N32, 64, and O32. See Table 1-1, for a summary of the ABIs, their features, and their relationships to the MIPS ISAs.

Table 1-1. Features of the Application Binary Interfaces

 

N32 (-n32)

64 (-64)

O32 (-o32)

Default ISA

MIPS III

MIPS IV

MIPS II

Alternate ISA (Option)

MIPS IV (-mips4)

MIPS III (-mips3)

MIPS I (-mips1)

Floating Point Registers

32

32

16

Register Size

64 bits

64 bits

32 bits

int

32 bits

32 bits

32 bits

long int

32 bits

64 bits

32 bits

char*

32 bits

64 bits

32 bits

The instruction set architecture is the set of instructions recognized by a processor. It is the interface between the lowest level software and the processor. Table 1-2, shows the MIPS ISAs and the MIPS processors for which they were designed.

Table 1-2. ISAs and Targeted MIPS Processors

ISA

Target Processor

MIPS IV

R5000, R8000, R10000, R12000

MIPS III

R4000 (Rev 2.2 and later), R4400, R46000

MIPS II

R4000 (Rev. 2.1 and earlier)

MIPS I

R2000, R3000

Table 1-1, and Table 1-2 are intended to give an overview of ABIs and MIPS ISAs. They do not show such details as the default ISAs shown in Table 1-1, can be changed with the environmental variable SGI_ABI. Also, these tables do not indicate some facts such as an R10000-based, IRIX 6.3 O2 system does not support 64 MIPS IV, while an R10000-based IRIX 6.4, OCTANE system does support it. The release notes for your system, the MIPSpro Compiling and Performance Tuning Guide, and the cc(1), CC(1), and ABI(5) man pages, provide more information about these details.

N32, 64, and O32 Compilation

The differences between the N32, 64 and the O32 compilers include the following:

  • The code generated for N32 and 64 is much more highly optimized than that generated for O32. However, it may take longer to compile code for N32 and 64 due to the increased optimization performed.

  • The warning options used by the -woff option are different.

The following are the default compilation modes:

  • CC -64 is -mips4

  • CC -n32 is -mips3

  • CC -o32 is -mips2

Silicon Graphics recommends that most of your development be for the N32 ABI: -n32 -mips3 for R4x00 systems and -n32 -mips4 for R5000, R8000, R10000, and R12000 systems. This gives your program access to the full MIPS III instruction set for R4x00 systems. On systems that use the R5000 or above, it allows your program to use the MIPS IV instruction set with the lower overhead of a 32-bit address space. You can reserve the higher overhead -64 -mips4 option for those applications that need the 64-bit address space on R8000, R10000, and R12000 systems.

The general relationship between ABIs, ISAs, and the CPUs that can run them is shown in Table 1-3. Again, because of system variations, there are some exceptions to the combinations shown. Consult your system's man pages and release notes for more information.

Table 1-3. ISAs, ABIs, and Their Host CPUs

 

-n32

-64

-o32

-mips4

R12000, R10000, R8000, R5000

R12000, R10000, R8000

 

-mips3

R10000, R8000, R5000, R4600, R4400, R4000 (>=Rev. 2.2)

  

-mips2

  

R10000, R8000, R5000, R4600, R4400, R4000

-mips1

  

R10000, R8000, R5000, R4600, R4400, R4000, R3000



Note: The objects of one ABI are incompatible with those of another; they cannot be linked together.

See the following sources for additional information about O32, N32, and 64 compiling:

  • Refer to the MIPSpro N32 ABI Handbook for a primer on N32.

  • Refer to the MIPSpro Compiling and Performance Tuning Guide for a more complete discussion on how to set up the IRIX environment for the MIPSpro compilers or O32.

  • Refer to the MIPSpro 64-Bit Porting and Transition Guide for more information on N32 and 64 compilers.

Feature and ABI Changes in the MIPSpro C++ Compilers

Both the N32 and 64 compilers have features that were introduced in MIPSpro 6.2 and MIPSpro 7.0. Of these features, only exception handling is available in the O32 compiler; you have to protect your use of these features with the #ifdef construct if you want your code to be portable across all Silicon Graphics C++ compilers.

There is a builtin macro, __EDG_ABI_COMPATIBILITY_VERSION, which is undefined in the O32 compiler, but set to the integer value 229 in the 64 and N32 compilers. You can either use this macro to protect your use of the language features of the N32 and 64 compilers, or you can create your own #ifdef construct.

OpenMP API Multiprocessing Directives

MIPSpro C and C++ compilers support directives based on the OpenMP C/C++ Application Program Interface (API) standard. Programs that use these directives are portable and can be compiled by other compilers that support the OpenMP standard.

To enable recognition of the OpenMP directives, specify -mp on the cc or CC command line.

In addition to directives, the OpenMP C/C++ API describes several library functions and environment variables. Information on the library functions can be found on the omp_lock(3), omp_nested(3), and omp_threads(3) man pages. Information on the environment variables can be found on the pe_environ(5) man page.

See the MIPSpro C and C++ Pragmas manual for definitions and details about how to use the OpenMP #pragma directives.

Operators new[ ] and delete[ ]

The N32 and 64 compilers implement the array variants of operators new and delete from the draft C++ standard. All calls to allocate and deallocate arrays of objects using new Classname[n] and delete[] Classname go through operators new[] and delete[], respectively, instead of the operators new and delete. This implies the following:

  • If you override the global ::operator new(), you probably do not have to do anything else. The default library ::operator new[]() simply calls ::operator new() to allocate memory. It is recommended that you also redefine ::operator new[]() if you redefine ::operator new(). The same also applies to ::operator delete().

  • If you define a placement operator new(), such as:

    operator new(size_t, other parameters);

    you must define an additional operator new[](), such as:

    operator new[](size_t, other parameters)

    that is bound to calls of the following form:

    new(other parameters) Classname[n];

    If you do not do this, the compiler issues an error that an appropriate operator new[]() has not been declared.

  • The same applies for class-specific operator new() and operator delete(): You should also define a corresponding class-specific operator new[]() or operator delete[]().


Note: If you do not protect these declarations under a macro like the one described in the introduction to this section, you will get compiler errors with the O32 compiler.


Built-in bool Type

There is a built-in bool type in the 64 and N32 MIPSpro compilers (but not in the O32 compiler). The keywords true and false are now keywords when bool is supported as a built-in type, having values that are the bool equivalent of 1 and 0, respectively.

To take advantage of this type, which is portable between O32 and the MIPSpro compilers, you can declare a bool type for O32 as follows:

#ifndef _BOOL
/* bool not predefined */
typedef unsigned char bool;
static const bool false = 0;
static const bool true = 1;
#endif /* _BOOL */

The macro _BOOL is predefined to be 1 when the bool keyword is supported.


Note: The -LANG:bool=off option in the N32 and 64 compilers can be used to disable the built-in bool, true, and false keywords (in case you have already used any of these identifiers in your program), making it behave like the O32 compiler in this regard.


Built-in wchar_t Type

The type wchar_t is a keyword and built-in type in the two MIPSpro compilers. It is analogous to the wchar_t type defined in stddef.h. In fact, this file can be safely included into an N32 or 64 compile and will not interfere with the built-in wchar_t. When the compiler supports wchar_t, it also defines a macro called _WCHAR_T; this allows you to write code that is portable across the O32 and the two MIPSpro compilers.

For instance, you can now read and write wchar_t types directly. They will not be read and written as long types, but will actually be read and written as multi-byte characters during the execution of the program.

Because the built-in wchar_t is considered a distinct type, not a synonym for int or long, there is the potential for problems. For example, consider what happens if you attempt to pass a built-in wchar_t to a function that is overloaded on other integral types, but not specifically on wchar_t, as in the following example:

extern void foo(int);
extern void foo(long);

wchar_t w;
foo(w);    // OK in -o32, ERROR in -n32/-64

// The fix is to declare a variant for wchar_t, but only when
// __EDG_ABI_COMPATIBILITY_VERSION >= 229:
extern void foo(int);
extern void foo(long);
#if __EDG_ABI_COMPATIBILITY_VERSION >= 229
extern void foo(wchar_t);
#endif

If you do not #ifdef it this way, the O32 compiler will indicate that foo(long) has already been declared. This happens because in O32, wchar_t is a synonym for long.

The -LANG:wchar_t=off option can be used to disable recognition of the wchar_t keyword.

Exception Handling

Exception handling is on by default in the MIPSpro 7.x compilers in the N32 and 64 modes; it can be turned off by using the -LANG:exceptions=off option. It is also supported in the O32 compiler by using the -exceptions flag. The exception handling constructs of C++ permit you to write code that detects an abnormal execution state of the program and take appropriate action. For instance, if a garbage collector runs out of memory to allocate, the routine may signal an exception that can be handled by displaying an appropriate message and possibly increasing the allocatable heap size.

The Silicon Graphics C++ compilers provide mechanisms for catching (handling) exceptions in a different scope than the scope where they were raised. The implementation of exceptions ensures that there is no appreciable performance penalty for programs that do not throw exceptions.

The following example illustrates the basic syntactic constructs used in exception handling (try, throw, and catch):

Example 1-1. Exception Handling

void allocator() {
    ...
    if OutOfSpace()
      throw MemOverflowError();
    ...
}

main() {
    ...
    try { // Wrapper for code that may throw exceptions
      mem * = allocator();
      ...
    }

    catch (MemOverflowError) { // Exception handler
      cout << "Exception during allocate.";
      ...
    }
    ...
}

The allocator function raises an exception if it runs out of space. In the main program, the call to allocator() is enclosed within a try block, a program region where exceptions may be raised by throw. If an exception is raised in the call to allocator(), then control shifts to the catch clause which catches the memory overflow error and does suitable error handling.

The syntax of a try and catch block combination is as follows:

try {
    ...
}

// Catch exceptions of int thrown in the try block above
catch (int ) {
    ...
}

// Catch exceptions of float thrown in the try block above
catch(float ) {
    ...
}

// Catch ALL exceptions
catch(...) {
    ...
}

A try block can be followed by zero or more catch blocks. If no exceptions are raised during the execution of a try block, control shifts to immediately after the last catch block. If an exception is raised, that is to say an object is thrown, during the execution of a try block, then the catch block whose type specification matches the type of the thrown object is chosen and control transfers to that handler. The block headed by the catch(...) statement catches all exceptions.

Run-time Type Identification

This feature is available only in 7.0 and later versions of the two MIPSpro compilers. C++ provides static type checking, which helps detect compile-time type errors. However, there are situations in which the type of an object may be known only at run time and it becomes necessary to provide some form of type safety. Specifically, given a pointer to an object of a base class, you may want to determine whether it is, in fact, a pointer to an object of a specific derived class of that base class. Consider the following example:

Example 1-2. Run-time Type Identification (RTTI)

class base {
    public:
      virtual ~base() {};
}
Class derived : base {
    public:
      void ctor() {};
}

You may want to write a function that takes as argument a pointer to base, and calls ctor() only if that pointer is in fact a pointer to derived. To do this, you need a mechanism for determining whether a pointer to a given base class is in fact a pointer to a derived class. You can do this by using the dynamic-cast facility of C++:

f(base *pb) {
    if (derived *pd = dynamic_cast <derived *> (pb))
      pd -> ctor();
    ...
}

If the pointer pb is actually pointing to an object of the derived class rather than of the base class, the dynamic_cast operation returns the pointer. Otherwise, dynamic_cast returns 0.

In addition to dynamic_cast, C++ also provides an operator typeid which determines the exact type of an object. This operator returns a reference to a standard library type called type_info, which represents the type name of its argument.

Other ABI Changes

There are three other ABI changes:

  • The object layout has been modified somewhat between O32 and N32, specifically for classes that have virtual base classes inherited along more than one path. This may cause you some difficulties if your code depends on the exact layout of such objects.

  • Name mangling for O32 is slightly different from that of the N32 and 64 compilers. The function designator F (as in foo__FUi) in O32 becomes G in the mangled names of the two MIPSpro compilers (as infoo__GUi).

    If your assembly, C, or Fortran code has calls to mangled C++ names, or if you execute dynamic name lookups using dlsym on mangled C++ names, you may be affected.

  • Virtual function tables, which are internal to the implementation, have been expanded in the two MIPSpro compilers. Subobjects (single occurrences of a base class) are no longer limited to 32KB and 32,000 virtual functions.

    The limits are now 4GB subobject sizes (both in N32 and 64) and 4 billion virtual functions. The subobject size is not even an issue unless the class is larger than 4GB and is a non-rightmost base class in a multiple-inheritance situation; for single-inheritance, the base class size should never be an issue.

C++ Libraries

By default, all C++ programs link with the complete C++ standard library which contains all of the iostream library functions, as well as the C++ storage allocation operators ::new and ::delete.

As of the 7.3 release, there are two complex libraries and two I/O libraries. The old complex library is used with -o32, which requires explicit linking using the -lcomplex option; the new complex library is used for -n32 and -64, which is linked by default. The old I/O library is used for -o32, -n32, and -64; the new I/O library is used only for -n32 and -64 when the -LANG:std option is specified. You must continue to link with the math library by specifying the -lm option.

Silicon Graphics also provides the complex arithmetic library. To use this package with the -o32, you must explicitly link with this library. For example,

CC -o32 complexapp.c++ -lcomplex

For more information on the complex and iostream libraries, see the C++ standard.

Debugging

You can debug your C++ programs with dbx (a source-level debugger for C, C++, Fortran, and assembly language) or with the MIPSpro WorkShop Debugger (a graphical, interactive, source-level debugging tool). For more information on dbx, see the dbx User's Guide. For additional information on the WorkShop Debugger, see the Developer Magic: Debugger User's Guide.