[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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] | [ ? ] |
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.
Constraint_Error
), or
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] | [ ? ] |
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:
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.
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.
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] | [ ? ] |
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] | [ ? ] |
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] | [ ? ] |
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] | [ ? ] |