Contents Index Search Previous Next
11.6 Exceptions and Optimization
1
[
{language-defined check}
{check (language-defined)}
{run-time error} {error
(run-time)} {optimization}
{efficiency} This
clause gives permission to the implementation to perform certain ``optimizations''
that do not necessarily preserve the canonical semantics.]
Dynamic Semantics
2
{canonical semantics}
The rest of this International Standard (outside
this clause) defines the
canonical semantics of the language.
[The canonical semantics of a given (legal) program determines a set
of possible external effects that can result from the execution of the
program with given inputs.]
2.a
Ramification: Note that
the canonical semantics is a set of possible behaviors, since some reordering,
parallelism, and non-determinism is allowed by the canonical semantics.
2.b
Discussion:
The following parts of the canonical semantics are of particular
interest to the reader of this clause:
2.c
- Behavior in the
presence of abnormal objects and objects with invalid representations
(see 13.9.1).
2.d
- Various actions
that are defined to occur in an arbitrary order.
2.e
- Behavior in the
presence of a misuse of Unchecked_Deallocation, Unchecked_Access, or
imported or exported entity (see Section 13).
3
[As explained in
1.1.3,
``
Conformity of an Implementation with the Standard'',
the external effect of a program is defined in terms of its interactions
with its external environment. Hence, the implementation can perform
any internal actions whatsoever, in any order or in parallel, so long
as the external effect of the execution of the program is one that is
allowed by the canonical semantics, or by the rules of this clause.]
3.a
Ramification: Note that
an optimization can change the external effect of the program, so long
as the changed external effect is an external effect that is allowed
by the semantics. Note that the canonical semantics of an erroneous execution
allows any external effect whatsoever. Hence, if the implementation can
prove that program execution will be erroneous in certain circumstances,
there need not be any constraints on the machine code executed in those
circumstances.
Implementation Permissions
4
The following additional
permissions are granted to the implementation:
5
- {extra permission
to avoid raising exceptions} {undefined
result} An implementation need not always
raise an exception when a language-defined check fails. Instead, the
operation that failed the check can simply yield an undefined result.
The exception need be raised by the implementation only if, in the absence
of raising it, the value of this undefined result would have some effect
on the external interactions of the program. In determining this, the
implementation shall not presume that an undefined result has a value
that belongs to its subtype, nor even to the base range of its type,
if scalar. [Having removed the raise of the exception, the canonical
semantics will in general allow the implementation to omit the code for
the check, and some or all of the operation itself.]
5.a
Ramification: Even without
this permission, an implementation can always remove a check if it cannot
possibly fail.
5.b
Reason: We express the
permission in terms of removing the raise, rather than the operation
or the check, as it minimizes the disturbance to the canonical semantics
(thereby simplifying reasoning). By allowing the implementation to omit
the raise, it thereby does not need to "look" at what happens
in the exception handler to decide whether the optimization is allowed.
5.c
Discussion: The implementation
can also omit checks if they cannot possibly fail, or if they could only
fail in erroneous executions. This follows from the canonical semantics.
5.d
Implementation
Note: This permission is intended to allow normal "dead code
removal" optimizations, even if some of the removed code might have
failed some language-defined check. However, one may not eliminate the
raise of an exception if subsequent code presumes in some way that the
check succeeded. For example:
5.e
if X * Y > Integer'Last then
Put_Line("X * Y overflowed");
end if;
exception
when others =>
Put_Line("X * Y overflowed");
5.e.1
If X*Y does overflow,
you may not remove the raise of the exception if the code that does the
comparison against Integer'Last presumes that it is comparing it with
an in-range Integer value, and hence always yields False.
5.f
As
another example where a raise may not be eliminated:
5.g
subtype Str10 is String(1..10);
type P10 is access Str10;
X : P10 := null;
begin
if X.all'Last = 10 then
Put_Line("Oops");
end if;
5.g.1
In the above code, it would
be wrong to eliminate the raise of Constraint_Error on the "X.all"
(since X is null), if the code to evaluate 'Last always yields 10 by
presuming that X.all belongs to the subtype Str10, without even "looking."
6
- {extra permission
to reorder actions} If an exception is
raised due to the failure of a language-defined check, then upon reaching
the corresponding exception_handler
(or the termination of the task, if none), the external interactions
that have occurred need reflect only that the exception was raised somewhere
within the execution of the sequence_of_statements
with the handler (or the task_body),
possibly earlier (or later if the interactions are independent of the
result of the checked operation) than that defined by the canonical semantics,
but not within the execution of some abort-deferred operation or independent
subprogram that does not dynamically enclose the execution of the construct
whose check failed. {independent subprogram}
An independent subprogram is one that is defined
outside the library unit containing the construct whose check failed,
and has no Inline pragma applied
to it. {normal state of an object} {abnormal
state of an object [partial]} {disruption
of an assignment [partial]} Any assignment
that occurred outside of such abort-deferred operations or independent
subprograms can be disrupted by the raising of the exception, causing
the object or its parts to become abnormal, and certain subsequent uses
of the object to be erroneous, as explained in 13.9.1.
6.a
Reason: We allow such
variables to become abnormal so that assignments (other than to atomic
variables) can be disrupted due to ``imprecise'' exceptions or instruction
scheduling, and so that assignments can be reordered so long as the correct
results are produced in the end if no language-defined checks fail.
6.b
Ramification: If a check
fails, no result dependent on the check may be incorporated in an external
interaction. In other words, there is no permission to output meaningless
results due to postponing a check.
6.c
Discussion: We believe
it is important to state the extra permission to reorder actions in terms
of what the programmer can expect at run time, rather than in terms of
what the implementation can assume, or what transformations the implementation
can perform. Otherwise, how can the programmer write reliable programs?
6.d
This clause has two conflicting
goals: to allow as much optimization as possible, and to make program
execution as predictable as possible (to ease the writing of reliable
programs). The rules given above represent a compromise.
6.e
Consider the two extremes:
6.f
The extreme conservative rule
would be to delete this clause entirely. The semantics of Ada would be
the canonical semantics. This achieves the best predictability. It sounds
like a disaster from the efficiency point of view, but in practice, implementations
would provide modes in which less predictability but more efficiency
would be achieved. Such a mode could even be the out-of-the-box mode.
In practice, implementers would provide a compromise based on their customer's
needs. Therefore, we view this as one viable alternative.
6.g
The extreme liberal rule would
be ``the language does not specify the execution of a program once a
language-defined check has failed; such execution can be unpredictable.''
This achieves the best efficiency. It sounds like a disaster from the
predictability point of view, but in practice it might not be so bad.
A user would have to assume that exception handlers for exceptions raised
by language-defined checks are not portable. They would have to isolate
such code (like all nonportable code), and would have to find out, for
each implementation of interest, what behaviors can be expected. In practice,
implementations would tend to avoid going so far as to punish their customers
too much in terms of predictability.
6.h
The most important thing about
this clause is that users understand what they can expect at run time,
and implementers understand what optimizations are allowed. Any solution
that makes this clause contain rules that can interpreted in more than
one way is unacceptable.
6.i
We have chosen a compromise
between the extreme conservative and extreme liberal rules. The current
rule essentially allows arbitrary optimizations within a library unit
and inlined subprograms reachable from it, but disallow semantics-disrupting
optimizations across library units in the absence of inlined subprograms.
This allows a library unit to be debugged, and then reused with some
confidence that the abstraction it manages cannot be broken by bugs outside
the library unit.
7
3 The permissions granted
by this clause can have an effect on the semantics of a program only
if the program fails a language-defined check.
Wording Changes from Ada 83
7.a
RM83-11.6
was unclear. It has been completely rewritten here; we hope this version
is clearer. Here's what happened to each paragraph of RM83-11.6:
7.b
- Paragraphs 1
and 2 contain no semantics; they are merely pointing out that anything
goes if the canonical semantics is preserved. We have similar introductory
paragraphs, but we have tried to clarify that these are not granting
any ``extra'' permission beyond what the rest of the document allows.
7.c
- Paragraphs 3
and 4 are reflected in the ``extra permission to reorder actions''. Note
that this permission now allows the reordering of assignments in many
cases.
7.d
- Paragraph 5 is
moved to 4.5, ``Operators
and Expression Evaluation'', where operator association is discussed.
Hence, this is no longer an ``extra permission'' but is part of the canonical
semantics.
7.e
- Paragraph 6 now
follows from the general permission to store out-of-range values for
unconstrained subtypes. Note that the parameters and results of all the
predefined operators of a type are of the unconstrained subtype of the
type.
7.f
- Paragraph 7 is
reflected in the ``extra permission to avoid raising exceptions''.
7.g
We moved clause 11.5,
``Suppressing Checks'' from after 11.6 to
before 11.6, in order to preserve the famous number ``11.6'' (given the
changes to earlier clauses in Section 11).
Contents Index Search Previous Next Legal