5.3.5. ARM extensions to the C9X interface

ARM provides some extensions to the C9X interface, to enable it to do everything that the ARM floating-point environment is capable of. This includes trapping and untrapping individual exception types, and also installing custom trap handlers.

The types fenv_t and fexcept_t are not defined by C9X to be anything in particular. ARM defines them both to be the same structure type:

typedef struct {
    unsigned statusword;
    __ieee_handler_t invalid_handler;
    __ieee_handler_t divbyzero_handler;
    __ieee_handler_t overflow_handler;
    __ieee_handler_t underflow_handler;
    __ieee_handler_t inexact_handler;
} fenv_t, fexcept_t;

The members of the above structure are:

Writing custom exception trap handlers

If you want to install a custom exception trap handler, declare it as a function like this:

__softfp__ieee_value_t myhandler(__ieee_value_t op1,
                                 __ieee_value_t op2,
                                 __ieee_edata_t edata);

The parameters to this function are:

  • op1 and op2 are used to give the operands, or the intermediate result, for the operation that caused the exception:

    • For the Invalid Operation and Divide by Zero exceptions, the original operands are supplied.

    • For the Inexact Result exception, all that is supplied is the ordinary result that would have been returned anyway. This is provided in op1.

    • For the Overflow exception, an intermediate result is provided. This result is calculated by working out what the operation would have returned if the exponent range had been big enough, and then adjusting the exponent so that it fits in the format. The exponent is adjusted by 192 (0xC0) in single precision, and by 1536 (0x600) in double precision.

      If Overflow happens when converting a double to a float, the result is supplied in double format, rounded to single precision, with the exponent biased by 192.

    • For the Underflow exception, a similar intermediate result is produced, but the bias value is added to the exponent instead of being subtracted. The edata parameter also contains a flag to show whether the intermediate result has had to be rounded up, down, or not at all.

    The type __ieee_value_t is defined as a union of all the possible types that an operand can be passed as:

    typedef union {
        float f;
        float s;
        double d;
        int i;
        unsigned int ui;
        long long l;
        unsigned long long ul;
        struct { int word1, word2; } str;
    } __ieee_value_t;
    
  • edata contains flags that give details about the exception that occurred, and what operation was being performed. (The type __ieee_edata_t is a synonym for unsigned int.)

  • The return value from the function is used as the result of the operation that caused the exception.

The flags contained in edata are:

  • edata & FE_EX_RDIR is nonzero if the intermediate result in Underflow was rounded down, and 0 if it was rounded up or not rounded. (The difference between the last two is given in the Inexact Result bit.) This bit is meaningless for any other type of exception.

  • edata & FE_EX_exception is nonzero if the given exception (INVALID, DIVBYZERO, OVERFLOW, UNDERFLOW or INEXACT) occurred. This enables you to:

    • use the same handler function for more than one exception type (the function can test these bits to tell what exception it is supposed to handle)

    • determine whether Overflow and Underflow intermediate results have been rounded or are exact.

    Because the FE_EX_INEXACT bit can be set in combination with either FE_EX_OVERFLOW or FE_EX_UNDERFLOW, you must determine the type of exception that actually occurred by testing Overflow and Underflow before testing Inexact.

  • edata & FE_EX_FLUSHZERO is nonzero if the FZ bit was set when the operation was performed (see The __ieee_status function).

  • edata & FE_EX_ROUND_MASK gives the rounding mode that applies to the operation. This is normally the same as the current rounding mode, unless the operation that caused the exception was a routine such as _ffix, that always rounds toward zero. The available rounding mode values are FE_EX_ROUND_NEAREST, FE_EX_ROUND_PLUSINF, FE_EX_ROUND_MINUSINF and FE_EX_ROUND_ZERO.

  • edata & FE_EX_INTYPE_MASK gives the type of the operands to the function, as one of the type values shown in Table 5.8.

    Table 5.8. FE_EX_INTYPE_MASK operand type flags

    FlagOperand type
    FE_EX_INTYPE_FLOATfloat
    FE_EX_INTYPE_DOUBLEdouble
    FE_EX_INTYPE_INTint
    FE_EX_INTYPE_UINTunsigned int
    FE_EX_INTYPE_LONGLONGlong long
    FE_EX_INTYPE_ULONGLONGunsigned long long
  • edata & FE_EX_OUTTYPE_MASK gives the type of the operands to the function, as one of the type values shown in Table 5.9.

    Table 5.9. FE_EX_OUTTYPE_MASK operand type flags

    FlagOperand type
    FE_EX_OUTTYPE_FLOATfloat
    FE_EX_OUTTYPE_DOUBLEdouble
    FE_EX_OUTTYPE_INTint
    FE_EX_OUTTYPE_UINTunsigned int
    FE_EX_OUTTYPE_LONGLONGlong long
    FE_EX_OUTTYPE_ULONGLONGunsigned long long
  • edata & FE_EX_FN_MASK gives the nature of the operation that caused the exception, as one of the operation codes shown in Table 5.10.

    Table 5.10. FE_EX_FN_MASK operation type flags

    FlagOperation type
    FE_EX_FN_ADDAddition.
    FE_EX_FN_SUBSubtraction.
    FE_EX_FN_MULMultiplication.
    FE_EX_FN_DIVDivision.
    FE_EX_FN_REMRemainder.
    FE_EX_FN_RNDRound to integer.
    FE_EX_FN_SQRTSquare root.
    FE_EX_FN_CMPCompare.
    FE_EX_FN_CVTConvert between formats.
    FE_EX_FN_RAISEThe exception was raised explicitly, by feraiseexcept or feupdateenv. In this case almost nothing in the edata word is valid.

    When the operation is a comparison, the result must be returned as if it were an int, and must be one of the four values shown in Table 5.11.

    Input and output types are the same for all operations except Compare and Convert.

    Table 5.11. FE_EX_CMPRET_MASK comparison type flags

    FlagComparison
    FE_EX_CMPRET_LESSop1 is less than op2
    FE_EX_CMPRET_EQUALop1 is equal to op2
    FE_EX_CMPRET_GREATERop1 is greater than op2
    FE_EX_CMPRET_UNORDEREDop1 and op2 are not comparable

Example 5.1 shows a custom exception handler. Suppose you are converting some Fortran code into C. The Fortran numerical standard requires 0 divided by 0 to be 1, whereas IEEE 754 defines 0 divided by 0 to be an Invalid Operation and so by default it returns a quiet NaN. The Fortran code is likely to rely on this behavior, and rather than modifying the code, it is probably easier to make 0 divided by 0 return 1.

A handler function that does this is shown in Example 5.1.

Example 5.1. 

__softfp __ieee_value_t myhandler(__ieee_value_t op1, __ieee_value_t op2,
                                 __ieee_edata_t edata)
{
    __ieee_value_t ret;
    if ((edata & FE_EX_FN_MASK) == FE_EX_FN_DIV) {
        if ((edata & FE_EX_INTYPE_MASK) == FE_EX_INTYPE_FLOAT) {
            if (op1.f == 0.0 && op2.f == 0.0) {
                ret.f = 1.0;
                return ret;
            }
        } 
        if ((edata & FE_EX_INTYPE_MASK) == FE_EX_INTYPE_DOUBLE) {
            if (op1.d == 0.0 && op2.d == 0.0) {
                ret.d = 1.0;
                return ret;
            }
        }
    }
    /* For all other invalid operations, raise SIGFPE as usual */
    raise(SIGFPE);
}

Install the handler function as follows:

fenv_t env;
fegetenv(&env);
env.statusword |= FE_IEEE_MASK_INVALID;
env.invalid_handler = myhandler;
fesetenv(&env);

After the handler is installed, dividing 0.0 by 0.0 returns 1.0.

Exception trap handling by signals

If an exception is trapped but the trap handler address is set to NULL, a default trap handler is used.

The default trap handler raises a SIGFPE signal. The default handler for SIGFPE prints an error message and terminates the program.

If you trap SIGFPE, you can declare your signal handler function to have a second parameter that tells you the type of floating-point exception that occurred. This feature is provided for compatibility with Microsoft products. The values are _FPE_INVALID, _FPE_ZERODIVIDE, _FPE_OVERFLOW, _FPE_UNDERFLOW and _FPE_INEXACT. They are defined in float.h. For example:

void sigfpe(int sig, int etype) {
    printf("SIGFPE (%s)\n",
           etype == _FPE_INVALID ? "Invalid Operation" :
           etype == _FPE_ZERODIVIDE ? "Divide by Zero" :
           etype == _FPE_OVERFLOW ? "Overflow" :
           etype == _FPE_UNDERFLOW ? "Underflow" :
           etype == _FPE_INEXACT ? "Inexact Result" :
           "Unknown");
}
signal(SIGFPE, (void(*)(int))sigfpe);

To generate your own SIGFPE signals with this extra information, you can call the function __rt_raise instead of the ANSI function raise. In Example 5.1, instead of:

    raise(SIGFPE);

it is better to code:

    __rt_raise(SIGFPE, _FPE_INVALID);

__rt_raise is declared in rt_misc.h.

Copyright © 1999-2001 ARM Limited. All rights reserved.ARM DUI 0067D
Non-Confidential