GNAT GPL User's Guide: Overflow Check Handling in GNAT

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

# D. Overflow Check Handling in GNAT

> D.1 Background |  |
> D.2 Overflow Checking Modes in GNAT |  |
> D.3 Specifying the Desired Mode |  |
> D.4 Default Settings |  |
> D.5 Implementation Notes |  |

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## D.1 Background

Overflow checks are checks that the compiler may make to ensure that intermediate results are not out of range. For example:

       A : Integer;
       ...
       A := A + 1;

if `A` has the value `Integer'Last`, then the addition may cause overflow since the result is out of range of the type `Integer`. In this case `Constraint_Error` will be raised if checks are enabled.

A trickier situation arises in examples like the following:

      A, C : Integer;
      ...
      A := (A + 1) + C;

where `A` is `Integer'Last` and `C` is `-1`. Now the final result of the expression on the right hand side is `Integer'Last` which is in range, but the question arises whether the intermediate addition of `(A + 1)` raises an overflow error.

The (perhaps surprising) answer is that the Ada language definition does not answer this question. Instead it leaves it up to the implementation to do one of two things if overflow checks are enabled.

- raise an exception (`Constraint_Error`), or

- yield the correct mathematical result which is then used in subsequent operations.

If the compiler chooses the first approach, then the assignment of this example will indeed raise `Constraint_Error` if overflow checking is enabled, or result in erroneous execution if overflow checks are suppressed.

But if the compiler chooses the second approach, then it can perform both additions yielding the correct mathematical result, which is in range, so no exception will be raised, and the right result is obtained, regardless of whether overflow checks are suppressed.

Note that in the first example an exception will be raised in either case, since if the compiler gives the correct mathematical result for the addition, it will be out of range of the target type of the assignment, and thus fails the range check.

This lack of specified behavior in the handling of overflow for intermediate results is a source of non-portability, and can thus be problematic when programs are ported. Most typically this arises in a situation where the original compiler did not raise an exception, and then the application is moved to a compiler where the check is performed on the intermediate result and an unexpected exception is raised.

Furthermore, when using Ada 2012's preconditions and other assertion forms, another issue arises. Consider:

         procedure P (A, B : Integer) with
           Pre => A + B <= Integer'Last;

One often wants to regard arithmetic in a context like this from a mathematical point of view. So for example, if the two actual parameters for a call to `P` are both `Integer'Last`, then the precondition should be regarded as False. If we are executing in a mode with run-time checks enabled for preconditions, then we would like this precondition to fail, rather than raising an exception because of the intermediate overflow.

However, the language definition leaves the specification of whether the above condition fails (raising `Assert_Error`) or causes an intermediate overflow (raising `Constraint_Error`) up to the implementation.

The situation is worse in a case such as the following:

         procedure Q (A, B, C : Integer) with
           Pre => A + B + C <= Integer'Last;

Consider the call

         Q (A => Integer'Last, B => 1, C => -1);

From a mathematical point of view the precondition is True, but at run time we may (but are not guaranteed to) get an exception raised because of the intermediate overflow (and we really would prefer this precondition to be considered True at run time).

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## D.2 Overflow Checking Modes in GNAT

To deal with the portability issue, and with the problem of mathematical versus run-time interpretation of the expressions in assertions, GNAT provides comprehensive control over the handling of intermediate overflow. GNAT can operate in three modes, and furthemore, permits separate selection of operating modes for the expressions within assertions (here the term "assertions" is used in the technical sense, which includes preconditions and so forth) and for expressions appearing outside assertions.

The three modes are:

- Use base type for intermediate operations (`STRICT`)

  In this mode, all intermediate results for predefined arithmetic operators are computed using the base type, and the result must be in range of the base type. If this is not the case then either an exception is raised (if overflow checks are enabled) or the execution is erroneous (if overflow checks are suppressed). This is the normal default mode.

- Most intermediate overflows avoided (`MINIMIZED`)

  In this mode, the compiler attempts to avoid intermediate overflows by using a larger integer type, typically `Long_Long_Integer`, as the type in which arithmetic is performed for predefined arithmetic operators. This may be slightly more expensive at run time (compared to suppressing intermediate overflow checks), though the cost is negligible on modern 64-bit machines. For the examples given earlier, no intermediate overflows would have resulted in exceptions, since the intermediate results are all in the range of `Long_Long_Integer` (typically 64-bits on nearly all implementations of GNAT). In addition, if checks are enabled, this reduces the number of checks that must be made, so this choice may actually result in an improvement in space and time behavior.

  However, there are cases where `Long_Long_Integer` is not large enough, consider the following example:

             procedure R (A, B, C, D : Integer) with
               Pre => (A**2 * B**2) / (C**2 * D**2) <= 10;

  where `A` = `B` = `C` = `D` = `Integer'Last`. Now the intermediate results are out of the range of `Long_Long_Integer` even though the final result is in range and the precondition is True (from a mathematical point of view). In such a case, operating in this mode, an overflow occurs for the intermediate computation (which is why this mode says most intermediate overflows are avoided). In this case, an exception is raised if overflow checks are enabled, and the execution is erroneous if overflow checks are suppressed.

- All intermediate overflows avoided (`ELIMINATED`)

  In this mode, the compiler avoids all intermediate overflows by using arbitrary precision arithmetic as required. In this mode, the above example with `A**2 * B**2` would not cause intermediate overflow, because the intermediate result would be evaluated using sufficient precision, and the result of evaluating the precondition would be True.

  This mode has the advantage of avoiding any intermediate overflows, but at the expense of significant run-time overhead, including the use of a library (included automatically in this mode) for multiple-precision arithmetic.

  This mode provides cleaner semantics for assertions, since now the run-time behavior emulates true arithmetic behavior for the predefined arithmetic operators, meaning that there is never a conflict between the mathematical view of the assertion, and its run-time behavior.

  Note that in this mode, the behavior is unaffected by whether or not overflow checks are suppressed, since overflow does not occur. It is possible for gigantic intermediate expressions to raise `Storage_Error` as a result of attempting to compute the results of such expressions (e.g. `Integer'Last ** Integer'Last`) but overflow is impossible.

Note that these modes apply only to the evaluation of predefined arithmetic, membership, and comparison operators for signed integer aritmetic.

For fixed-point arithmetic, checks can be suppressed. But if checks are enabled then fixed-point values are always checked for overflow against the base type for intermediate expressions (that is such checks always operate in the equivalent of `STRICT` mode).

For floating-point, on nearly all architectures, `Machine_Overflows` is False, and IEEE infinities are generated, so overflow exceptions are never raised. If you want to avoid infinities, and check that final results of expressions are in range, then you can declare a constrained floating-point type, and range checks will be carried out in the normal manner (with infinite values always failing all range checks).

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## D.3 Specifying the Desired Mode

The desired mode of for handling intermediate overflow can be specified using either the `Overflow_Mode` pragma or an equivalent compiler switch. The pragma has the form

        pragma Overflow_Mode ([General =>] MODE [, [Assertions =>] MODE]);

where `MODE` is one of

- `STRICT`: intermediate overflows checked (using base type)
- `MINIMIZED`: minimize intermediate overflows
- `ELIMINATED`: eliminate intermediate overflows

The case is ignored, so `MINIMIZED`, `Minimized` and `minimized` all have the same effect.

If only the `General` parameter is present, then the given `MODE` applies to expressions both within and outside assertions. If both arguments are present, then `General` applies to expressions outside assertions, and `Assertions` applies to expressions within assertions. For example:

       pragma Overflow_Mode
         (General => Minimized, Assertions => Eliminated);

specifies that general expressions outside assertions be evaluated in "minimize intermediate overflows" mode, and expressions within assertions be evaluated in "eliminate intermediate overflows" mode. This is often a reasonable choice, avoiding excessive overhead outside assertions, but assuring a high degree of portability when importing code from another compiler, while incurring the extra overhead for assertion expressions to ensure that the behavior at run time matches the expected mathematical behavior.

The `Overflow_Mode` pragma has the same scoping and placement rules as pragma `Suppress`, so it can occur either as a configuration pragma, specifying a default for the whole program, or in a declarative scope, where it applies to the remaining declarations and statements in that scope.

Note that pragma `Overflow_Mode` does not affect whether overflow checks are enabled or suppressed. It only controls the method used to compute intermediate values. To control whether overflow checking is enabled or suppressed, use pragma `Suppress` or `Unsuppress` in the usual manner

Additionally, a compiler switch ``-gnato?'` or ``-gnato??'` can be used to control the checking mode default (which can be subsequently overridden using pragmas).

Here ``?`' is one of the digits ``1`' through ``3`':

- `1`: use base type for intermediate operations (`STRICT`)
- `2`: minimize intermediate overflows (`MINIMIZED`)
- `3`: eliminate intermediate overflows (`ELIMINATED`)

As with the pragma, if only one digit appears then it applies to all cases; if two digits are given, then the first applies outside assertions, and the second within assertions. Thus the equivalent of the example pragma above would be ``-gnato23'`.

If no digits follow the ``-gnato'`, then it is equivalent to ``-gnato11'`, causing all intermediate operations to be computed using the base type (`STRICT` mode).

In addition to setting the mode used for computation of intermediate results, the `-gnato` switch also enables overflow checking (which is suppressed by default). It thus combines the effect of using a pragma `Overflow_Mode` and pragma `Unsuppress`.

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## D.4 Default Settings

The default mode for overflow checks is

       General => Strict

which causes all computations both inside and outside assertions to use the base type. In addition overflow checks are suppressed.

This retains compatibility with previous versions of GNAT which suppressed overflow checks by default and always used the base type for computation of intermediate results.

The switch ``-gnato'` (with no digits following) is equivalent to

       General => Strict

which causes overflow checking of all intermediate overflows both inside and outside assertions against the base type. This provides compatibility with this switch as implemented in previous versions of GNAT.

The pragma `Suppress (Overflow_Check)` disables overflow checking, but it has no effect on the method used for computing intermediate results.

The pragma `Unsuppress (Overflow_Check)` enables overflow checking, but it has no effect on the method used for computing intermediate results.

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## D.5 Implementation Notes

In practice on typical 64-bit machines, the `MINIMIZED` mode is reasonably efficient, and can be generally used. It also helps to ensure compatibility with code imported from some other compiler to GNAT.

Setting all intermediate overflows checking (`CHECKED` mode) makes sense if you want to make sure that your code is compatible with any other possible Ada implementation. This may be useful in ensuring portability for code that is to be exported to some other compiler than GNAT.

The Ada standard allows the reassociation of expressions at the same precedence level if no parentheses are present. For example, `A+B+C` parses as though it were `(A+B)+C`, but the compiler can reintepret this as `A+(B+C)`, possibly introducing or eliminating an overflow exception. The GNAT compiler never takes advantage of this freedom, and the expression `A+B+C` will be evaluated as `(A+B)+C`. If you need the other order, you can write the parentheses explicitly `A+(B+C)` and GNAT will respect this order.

The use of `ELIMINATED` mode will cause the compiler to automatically include an appropriate arbitrary precision integer arithmetic package. The compiler will make calls to this package, though only in cases where it cannot be sure that `Long_Long_Integer` is sufficient to guard against intermediate overflows. This package does not use dynamic alllocation, but it does use the secondary stack, so an appropriate secondary stack package must be present (this is always true for standard full Ada, but may require specific steps for restricted run times such as ZFP).

Although `ELIMINATED` mode causes expressions to use arbitrary precision arithmetic, avoiding overflow, the final result must be in an appropriate range. This is true even if the final result is of type `[Long_[Long_]]Integer'Base`, which still has the same bounds as its associated constrained type at run-time.

Currently, the `ELIMINATED` mode is only available on target platforms for which `Long_Long_Integer` is 64-bits (nearly all GNAT platforms).

[ << ] | [ >> ] |  |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

This document was generated by GNAT Mailserver on April, 17 2014 using texi2html