Version 1.1 of ais/ai-00121.txt

Unformatted version of ais/ai-00121.txt version 1.1
Other versions for file ais/ai-00121.txt

!standard C.3.1 (07)          97-08-19 AI95-00121/04
!class binding interpretation 96-04-04
!status WG9 approved 96-12-07
!status ARG approved 11-0-1 96-10-07
!status work item (letter ballot was 10-1-1) 96-10-03
!status ARG approved 9-0-0 (subject to letter ballot) 96-06-17
!status work item 96-04-04
!status received 96-04-04
!priority High
!difficulty Hard
!subject Pragma Attach_Handler on Nested Objects
!summary 96-09-15
A program execution is erroneous if the handlers for a given interrupt attached via pragma Attach_Handler are not attached and detached in a stack-like (LIFO) order. In particular, when a protected object is finalized, if any of its procedures are attached to interrupts via pragma Attach_Handler, then if the most recently attached handler for the same interrupt is not the same as the one that was attached at the time the protected object was created, then execution is erroneous.
!question 96-07-23
C.3.1(7-8) says:
7 The Attach_Handler pragma is only allowed immediately within the protected_definition where the corresponding subprogram is declared. The corresponding protected_type_declaration or single_protected_declaration shall be a library level declaration.
7.a Discussion: in the case of a protected_type_declaration, an object_declaration of an object of that type need not be at library
level.
8 The Interrupt_Handler pragma is only allowed immediately within a protected_definition. The corresponding protected_type_declaration shall be a library level declaration. In addition, any object_declaration of such a type shall be a library level declaration.
Thus, nested objects are not allowed in the Interrupt_Handler case, but they are allowed in the Attach_Handler case.
C.3.1(12) says:
12 {finalization (of a protected object)} When a protected object is finalized, for any of its procedures that are attached to interrupts, the handler is detached. If the handler was attached by a procedure in the Interrupts package or if no user handler was previously attached to the interrupt, the default treatment is restored. Otherwise, [that is, if an Attach_Handler pragma was used,] the previous handler is restored.
12.a Discussion: Since only library-level protected procedures can be attached as handlers using the Interrupts package, the finalization discussed above occurs only as part of the finalization of all library-level packages in a partition.
Thus, in the Attach_Handler case, when the object is finalized, the "previous handler" is restored.
What is meant by "previous handler" here? Does this feature make sense in a multi-tasking situation?
!recommendation 96-07-23
(See summary.)
!wording 96-07-23
!discussion 96-07-23
The notion of restoring the "previous handler" only makes sense if objects are created and destroyed in a stack-like (LIFO) manner. In a multi-tasking program, it is possible to do otherwise -- for example, task A declares an object, then task B declares an object, then task A completes, destroying the first object, then task B completes, destroying the second object.
Several options exist:
Option 1: require every protected object with an Attach_Handler pragma to be at library level. This is clearly not what the RM says. It doesn't completely solve the problem, either -- one could create two objects on the heap, and Unchecked_Deallocate them in a non-LIFO order.
Option 2: define "previous handler" to be "the handler that was attached at the time the protected object was initialized". If that handler no longer exists, execution becomes erroneous. This means that if the programmer uses a LIFO order, it all works. If the programmer uses a non-LIFO order, handlers may get restored in a "surprising" order, and in some cases, erroneous execution will result.
Note that it is possible to have a LIFO order, even in a multi-tasking program. For example, first declare an object at library level. Create lots of tasks. Then, at some point, one of the tasks declares another object. Clearly, this second object will be finalized before the first one, which is what we want.
The implementation in this case is not so hard: store a pointer to the previous handler in the protected object, and blindly restore it on finalization.
Option 3: define the "previous handler" to be the one that was attached just before the current handler was attached. Again, execution is erroneous if one tries to restore a handler that no longer exists. Again, the implementation is not so hard: keep a stack of handlers. When a protected object is finalized, blindly pop one item off the stack, whether or not the protected object on the stack corresponds to the current handler.
If the programmer ensures a LIFO order, then the second and third possibilities are equivalent.
Option 4: an exception is raised if a LIFO order is not obeyed. That is, when a protected object is finalized, a check is made that this protected object corresponds to the currently-attached handler; if not, an exception is raised. In this case, the implementation can be as for the second or the third possibility, since they are equivalent.
Option 5: same as the fourth, except that execution becomes erroneous instead of raising an exception. The implementation is the same as for the fourth possibility, except that the check is omitted.
We choose Option 5. It is the programmer's responsibility to maintain LIFO order; otherwise execution is erroneous. We do not wish to impose overhead on implementations to check for LIFO order. If an implementation wishes to check, it can raise an exception as soon as LIFO order is disobeyed (thus implementing Option 4). We also do not wish to be restrictive, as would happen with Option 1.
Upon finalization, an implementation may restore either the handler that was installed at the time the current object was initialized, or the handler that was most recently installed before the current one. For all non-erroneous situations, these two are the same handler.
!appendix

!section C.3.1(07)
!subject Nesting of statically-attached interrupt handlers does not make sense
!reference RM95-C.3.1(7)
!reference RM95-C.3.1(8)
!reference RM95-C.3.1(10)
!reference RM95-C.3.1(12)
!from Offer Pazy 95-12-11
!reference 95-5413.a Offer Pazy 95-12-11>>
!discussion

Disclaimer:
-----------

I am not sure that this comment fits the formal definition of an AI.  The
semantics are very clear. As I explain below, I have just come to realize
that they were not fully thought about and there are usability and
implementation problems.

Background:
-----------

One of the design goals of the System Programming Annex was to allow the
nesting of interrupt handlers.  That is, we envisioned a requirement,
for large, multi-mode, systems, in which different modules are responsible 
for a given interrupt at different times, and hence different handlers need
to be attached, detached, and restored(!) dynamically. In general, the idea
in Ada in to limit the places where limitations are placed on non-library
level code. That is, everything that is possible for multi-mode system
which is organized as library-level subsystems should also be possible when
the modes are implemented as nested in one another. Anyway, this was the
rationale behind the above feature: A new mode (which is nested in another)
gets invoked, takes control of some of the interrupts, and when it exits,
responsibility for those interrupts is restored to the enclosing mode,
transparently to that mode (that is, the handlers are saved, a new one is
attached, and then the previous one is restored). The natural way to
express this in Ada was through the elaboration and finalization of nested
protected objects that have the pragma Attach_Handler applied to one of
their procedures.

Note a little-known rule in (7,8). The rules for dynamic attachment and
static attachment are different on purpose.  For both, the protected type
needs to be at a library level, but for the dynamic attachment (through the
Interrupts package), the protected object itself must also be at the library 
level, while for the static attachment, the object can be deeper.

The rationale (C.3.1) discusses these issues and the reasons of why we did 
not allow nesting in the dynamic case.

The problem:
------------

The problem is that this all idea is not fully thought about. I think we
had (mostly I had) a temporary fading of the brain in the sense that Ada
appear to be a multi-tasking environment and in such an environment the
above model does not make much sense. "Modes" are not necessarily nested 
within tasks. While a given scope is entered in one task, other tasks may
still be running "in a scope" that still has attached interrupts.  What
will happen is that one task will "steel" the interrupt from another
without the latter knowing about it, thus affecting the assumed environment
of that task.  This is one of the worst cases of unsynchronized use of
shared variables where the shared variable in this case is the HW component
connected to the interrupt. A "correct", but also impractical model would
be that the attachment of interrupts are assumed part of the task context
and are therefore being switched in and out on each context-switch.  This
is clearly undesired and not very feasible. Imagine task T1 talking to some 
piece of hardware, sending some request and then waiting for the response;
task T2 then is dispatched and it elaborates a PO to be the handler of the 
corresponding interrupt.  T2 now receives a meaningless interrupt while T1
loses its. Since this is affected by the preemption and scheduling rules, it 
is 
largely asynchronous. We did not specify any synchronization (or signalling) 
rules to require a disciplined usage of this capability, not that I see a 
simple way to define such rules.

One may claim that this is all a user problem and the designer should make
arrangements so this will never happen. While correct in principle, we
don't provide mechanisms to coordinate this. Any safe usage of this feature
will require resorting to either one task in the partition doing the
attachment/detachment, or using some other guidelines that essentially 
reduce 
the multi-tasking to a single task (or coroutines implemented as multiple
tasks). Nested attachment of handlers would make perfect sense in a single
tasking environment. But if this is the limitation, it can already be done 
with the dynamic form of attachment.

Implementing this requirement is also not trivial. Never mind, the useless
semantics, some sort of two-dimensional doubly linked-list has to be
implemented.  This is because there is no guarantee that handles will be
attached/detached in a LIFO order. When a PO (with statically-attached
handlers) finalizes, it is not guaranteed that the given handler was the
last one to be attached for that interrupt.  The handler may be anywhere in
the linked list and will have to be removed from the middle of the list
emanated from the interrupt. Not trivial in particular when it doe snot
have any real benefit.


Also, C.3.1(12) has a problem: It ends with "... the previous handler is
restored."  What is the previous handler? The one that was active the last?
It may already have gone by now, Also, when a PO finalizes, it is not
always the case, that "its" handler is the current one, so it will not be
always correct to affect the current attachment.

The solution(?):
----------------

While it is alway embarrassing to acknowledge a mistake, and it's a pity to
give up on a seemingly-useful feature, I don't see any other alternative
but to admit the mistake.  I don't see a way that the ARM language could be
re-interpreted to "quietly" solve this problem. Maybe the best thing is to
modify the above references and to disallow non-library level objects for
the static case as well.

Whatever the solution is, we should make sure that we don't lose the other 
feature of statically-attached handlers, and that is the ability to support
attachment at pre-elaboration time (i.e. for library-level objects). I
think that it is still important to be able to ensure that interrupt 
handlers 
are installed as early as possible in the program startup.

Offer Pazy
31 Robinwood Ave.
Boston, MA 02130
USA
(617)522-5988
pazy@world.std.com

****************************************************************

!section C.3.1(12)
!subject Attach_Handler and finalization
!reference RM95-C.3.1(12)
!from Laurent Guerby 97-08-08
!keywords interrupts, finalization
!reference 1997-15773.a Laurent Guerby  1997-8-8>>
!discussion

C.3.1:

| 12   {finalization (of a protected object)} When a protected object is
| finalized, for any of its procedures that are attached to interrupts, the
| handler is detached.  If the handler was attached by a procedure in the
| Interrupts package or if no user handler was previously attached to the
| interrupt, the default treatment is restored.  Otherwise, [that is, if an
| Attach_Handler pragma was used,] the previous handler is restored.

|         12.a   Discussion:  Since only library-level protected procedures can
|         be attached as handlers using the Interrupts package, the
|         finalization discussed above occurs only as part of the finalization
|         of all library-level packages in a partition.

   The 12.a annotation is partially incorrect since you can get
non-library level protected objects requiring such finalization by
declaring an object of a protected type with an Attach_Handler pragma.
(RM forbids only non-library level objects with a pragma
Interrupt_Handler in them.)

   Is this intented? It forces implementation to keep somehow a stack
of "previous handlers" for non library-level PO with Attach_Handler,
that sounds very dynamic for a "static" feature (Attach_Handler needs
more dynamic work than Ada.Interrupt and Interrupt_Handler...).

-- 
Laurent Guerby <guerby@gnat.com>, Team Ada.
   "Use the Source, Luke. The Source will be with you, always (GPL)."

****************************************************************

!section C.3.1(12)
!subject Attach_Handler and finalization
!reference RM95-C.3.1(12)
!reference 1997-15773.a Laurent Guerby 97-08-08
!reference AI95-00121
!keywords interrupts, finalization
!from Tucker Taft 97-08-11
!reference 1997-15775.a Tucker Taft 1997-8-11>>
!discussion

This problem is addressed by AI95-00121 (approved by WG9), which says:

  A program execution is erroneous if the handlers for a given interrupts
  attached via pragma Attach_Handler are not attached and detached in
  a stack-like (LIFO) order. ...

Hence, the implementation need not do anything special to handle the
case where finalizations occur in a non stack-like order.  It is possible
to create a doubly linked list of handlers, and survive detaching
of the non-current handler for an interrupt, but it is not necessary
for the implementation to do so.

-Tuck
--------------------
> C.3.1:
> 
> | 12   {finalization (of a protected object)} When a protected object is
> | finalized, for any of its procedures that are attached to interrupts, the
> | handler is detached.  If the handler was attached by a procedure in the
> | Interrupts package or if no user handler was previously attached to the
> | interrupt, the default treatment is restored.  Otherwise, [that is, if an
> | Attach_Handler pragma was used,] the previous handler is restored.
> 
> |         12.a   Discussion:  Since only library-level protected procedures can
> |         be attached as handlers using the Interrupts package, the
> |         finalization discussed above occurs only as part of the finalization
> |         of all library-level packages in a partition.
> 
>    The 12.a annotation is partially incorrect since you can get
> non-library level protected objects requiring such finalization by
> declaring an object of a protected type with an Attach_Handler pragma.
> (RM forbids only non-library level objects with a pragma
> Interrupt_Handler in them.)
> 
>    Is this intented? It forces implementation to keep somehow a stack
> of "previous handlers" for non library-level PO with Attach_Handler,
> that sounds very dynamic for a "static" feature (Attach_Handler needs
> more dynamic work than Ada.Interrupt and Interrupt_Handler...).
> 
> -- 
> Laurent Guerby <guerby@gnat.com>, Team Ada.
>    "Use the Source, Luke. The Source will be with you, always (GPL)."

****************************************************************

Questions? Ask the ACAA Technical Agent