Version 1.4 of ais/ai-00162.txt

Unformatted version of ais/ai-00162.txt version 1.4
Other versions for file ais/ai-00162.txt

!standard 03.10.02 (13)          03-12-01 AI95-00162/03
!class binding interpretation 96-09-10
!status work item 98-10-02
!status received 96-09-10
!priority Medium
!difficulty Hard
!subject Anonymous access types and tasks termination/controlled type finalization
!summary
A new kind of master is defined for objects associated with the evaluation of an allocator of an anonymous type, a function call, or an aggregate. The enclosing statement or declaration is the master of these anonymous objects. Finalization, task dependence, accessibility levels, and lifetimes for these objects are all defined in terms of this master.
!question
When an anonymous allocator, which is assigned to a formal access parameter, creates a task or creates a controlled object, who is the master of these things? More specifically, which construct is responsible for waiting for the task to terminate and for finalizing the task or the controlled object?
Here is an example of the problem:
with Ada.Finalization; use Ada.Finalization; package P is task type T; type CT is new Controlled with private; private ... end P;
with P; use P; procedure Test is procedure R (X: access T); procedure S (Y: access CT);
procedure Q (I: Integer) is begin -- (1) ... R (X => new T); -- (2a) ... S (Y => new CT); -- (2b) ... end Q;
procedure R (X: access T) is -- (3a) begin ... end R; procedure S (X: access CT) is -- (3a) begin ... end S;
begin ... Q (I); ... end Test;
Is the master of the anonymous allocator in either statement (2a or 2b) [new T or new CT]: - the innermost enclosing (normal) master (1), - some construct associated with the innermost enclosing statement (2a or 2b)
[as described by 7.6.1(13)] or
- the called subprogram's body (3a or 3b)?
!recommendation
(See summary.)
!wording
In 3.10.2(3) replace the first two sentences with
"The accessibility rules, which prevent dangling references, are written
in terms of accessibility levels, which reflect the run-time nesting of masters (see 7.6.1).
In 3.10.2(7), replace "enclosing master" with "enclosing unrestricted master".
Delete the second sentence of 3.10.2(13).
In 6.5(18), replace "master" with "unrestricted master".
In 7.6.1(3), replace
".. except in the case of a master: the execution of ... or an accept_statement."
with
".. except in the case of a master. An unrestricted master is the execution of a task_body, a block_statement, a subprogram_body, an entry_body, or an accept_statement. A restricted master is the execution of a statement, declaration, pragma, or entry_barrier condition which is not an unrestricted master.
Replace 7.6.1(13/1 - 13.1/1) with
A restricted master is a master of any anonymous object created as part of its execution by the evaluation of an allocator of an anonymous access type, a function call, or an aggregate except if the object is
- created as part of the execution of a more nested restricted master;
- created as part of a rename declaration which renames any part of the
object. In this case, the nearest enclosing unrestricted master of the rename declaration is defined to be a master of the object; the accessibility level of the object is defined to be the accessibility level of this master.
or
- created by an allocator of an anonymous access type which is used to initialize an access discriminant of another object. In this case, the masters of the allocated object are defined to be masters of the discriminated object; the accessibility level of the allocated object is defined to be that of the discriminated object. The allocated object is finalized when the discriminated object is finalized. The storage occupied by the allocated object is deallocated when the storage occupied by the discriminated object is deallocated.
For a statement, the finalization of such an anonymous object occurs
- when the statement is completed;
or
- immediately before beginning the execution of a nested statement;
or
- in the case of a potentially blocking operation, immediately before the operation blocks the current task
, whichever occurs first. Otherwise, this finalization occurs when the entity is completed.
The accessibility level of the anonymous object is the accessibility level of this master.
In 9.3(2), replace "master" with "unrestricted master". In 9.3(3), replace "master" with "unrestricted master". In 13.11(18), replace "master" with "unrestricted master".
Add at the end of 13.11.2(17).
If the object being reclaimed has an access discriminant which designates an object which which was created by an allocator of the (anonymous) type of the access discriminant, then the designated object should also be reclaimed.
!discussion
The Problem -----------
The language does not properly define the master of certain objects. These include anonymous objects associated with function calls, aggregates, and allocators of anonymous access types. Since this affects task dependence, the point at which finalization occurs, accessibility levels, and object lifetimes, this means that the run-time behavior of a program which creates such objects is unclear.
The Solution ------------
A new master is defined for objects associated with the evaluation of an allocator of an anonymous type, a function call, or an aggregate. For these objects only, the enclosing statement or declaration acts as a master. Finalization, task dependence, accessibility levels, and lifetimes for these objects are all defined in terms of this master.
This is not an incompatible change. This AI defines the run-time behavior of constructs whose behavior was previously undefined, but is otherwise consistent with the existing language definition.
Alternative Solutions ---------------------
One could define the master of an object of the sort discussed in this AI to be the innermost enclosing garden-variety master (i.e. task_body, block_statement, subprogram_body, entry_body, or accept_statement). This approach might be simpler to describe, but the lifetimes of objects would be extended too far.
The interactions with loops, in particular, would require that implementers stand on their heads in order to make life more difficult for users. In this example,
begin for I in 1 .. 100 loop Some_Procedure (Function_Call_Requiring_Finalization); end loop; end;
neither users nor implementers want to accumulate 100 unfinalized function result temps before finalizing the first one.
Other alternatives have been mentioned which only address special cases of the problem (e.g. for an allocator which is passed as an actual parameter of an anonymous access type, use the called routine as the master), but these do not make sense in the context of the more general problem.
Implementation Issues ---------------------
An implementation must somehow keep track of whether a given access discriminant component "belongs" to the enclosing discriminated object. In this example
type Designated is ... ; X : aliased Designated;
type T (Discrim : access Float) is limited null record; type T_Ref is access T; Ptr1 : T_Ref := new T (Discrim => X'access); Ptr2 : T_Ref := new T (Discrim => new Designated);
procedure Free is new Ada.Unchecked_Deallocation (T, T_Ref);
begin Free (Ptr1); Free (Ptr2);
finalization and deallocation of Ptr1.all does not include finalization and deallocation of Ptr1.Discrim.all. On the other hand, finalization of Ptr2.all includes finalization of Ptr2.Discrim.all (even if type T is has a user-defined Finalize routine) and Ptr2.Discrim.all is also deallocated when Ptr2.all is deallocated.
Only a non-component object of the discriminated type can "own" the object designated by its discriminant. For a non-allocated object, ownership is known statically. Thus, distributed overhead need only be incurred in the case of an allocated object with one or more access discriminants. One implementation strategy would be to prepend this information to the allocated object in much the same way that some implementations prepend dope vectors for allocated arrays when the designated subtype is unconstrained.
Open Issues ----------- None have been identified.
Technical Notes ---------------
1) In this example
My_Array_Of_Tasks : Task_Vector (1 .. Function_Call_Requiring_Finalization.Field);
, the declaration is the master of the function result object but is not the master of the tasks. This declaration, like every declaration, is now a master, but 9.3(3) now refers to "unrestricted masters" and the declaration is a "restricted master".
The distinction between two kinds of masters (tentatively called "unrestricted" and "restricted") is necessary because some places in the manual need to refer to all masters (e.g. the discussion in 7.6.1(4) of the finalization of a master or the definition in 3.10.2(6) of the accessibility level of a master) while other places need to refer only to old-style (i.e. unrestricted) masters (e.g. 3.10.2(7) and 9.3(3)). An earlier (uncirculated) version of this AI attempted to simply use the term "master" in both cases, but this didn't work well.
The terms "unrestricted master" and "restricted master" are not themselves very satisfactory. Does anyone have a better alternative?
2) It seems somewhat odd that
X : Some_Access_Type := new T1 (Access_Discrim => new T2);
and
subtype S is T1 (Access_Discrim => new T2); X : Some_Access_Type := new S;
mean very different things in a more nested scope than Some_Access_Type. The former will execute successfully; the latter will raise Program_Error due to a failed accessibility check. Users may not expect this, but this is not a change.
3) As per Tuck's and Randy's suggestions, this AI does not
address the complications in this area introduced by other unapproved AIs (230 and 325, in particular), although wording was chosen which should work without modification for 287 (again, as per Tuck's suggestion).
4) In this example
while Function_Call_Requiring_Finalization.Field loop
Some_Procedure_Call;
end loop;
, the function result object is finalized before the procedure is called.
5) In this example
X : T; Y : T renames Function_Call_Requiring_Finalization.Field;
, X and the function result object have the same master. This is consistent with AI-182; this is not a change.
6) The anonymous objects created as part of the evaluation of an allocator of
an anonymous access type may include a storage pool, in which case storage for the allocated object may be reclaimed when the storage pool is finalized; this will not occur until after the allocated object has been finalized.
7) The first sentence of 7.6.1(13/1) (the one ending in "until after
it is no longer accessible by any name") is replaced by the rule defining the master and accessibility level in this case. Note also that a function result cannot be passed as an actual parameter for a generic formal IN OUT parameter in a generic_instantiation, so no description of the dynamic semantics of this case was ever needed.
8) A compound statement which acts as a master need not be treated as though it
encloses any other master for purposes of determining accessibility levels. In this example
while Function_Call_Requiring_Finalization.Field loop
declare X : Some_Type; begin null; end;
end loop;
, an implementation may choose to view the master of X as being at a deeper accessibility level than the master of the function call or not; it doesn't matter.
9) Instead of defining compound statements to be masters, another approach
would be to define conditions, selector expressions for case statements, loop parameter specifications, delay_expressions, etc. to be masters. This would eliminate the need for the special treatment of entry_barrier conditions.
10) The discussion section of AI-169 mentions problems stemming from
"an anonymous object which is not associated with any particular master". Such an anonymous object now has a master.
11) In this example
function Dereference (X : access Some_Task_Type) return Task_Type is begin returns X.all; end;
Y : Task_Type renames Dereference (new Task_Type);
, the attempt to return X.all now raises Program_Error because of the change to 6.5(18). This is a good thing, as this would be very difficult to implement otherwise. The behavior of this example was previously unclear.
12) The execution of a block_statement is an unrestricted master, not a
restricted master.
13) We refer to "the master" of an object only in informal discussion.
A precise definition of this term would be straightforward, but so far it has not been necessary for RM wording.
--!corrigendum
!ACATS Test
ACATS C-Test(s) should be created to test that the correct master is used.
!appendix

!section 3.10(2)
!subject Anonymous access types and finalization
!reference RM95 3.10(2)
!reference RM95 3.7(27)
!reference RM95 7.6.1(11)
!from Pascal Leroy 96-08-20
!reference 96-5633.a Pascal Leroy 96-8-21>>
!discussion

The finalization of an object created by an allocator occurs when the master
of the ultimate ancestor of the access type is left, as stated in 7.6.1(11).
 In the case of access discriminants, 3.10(2) tells us that "an
access_definition defines an anonymous access type"; I take this to imply that
an anonymous access type is an "ultimate ancestor" in the sense of 7.6.1(11).
 In addition, 3.7(27) tells us that "an access_definition is elaborated when
the value of a corresponding access discriminant is defined"; I take this to
imply that the master of the anonymous access type is the master that
elaborated the discriminant constraint.

The interaction of these rules has the unfortunate consequence that it makes
it possible to reference an object after it has been finalized, as shown in
the following example:

    procedure P1 is
        type T (D : access Some_Controlled_Type) is ... ;

        procedure P2 is
            type T_Ref is access T;

            X : T_Ref;

            procedure P3 is
            begin
                X := new T (D => new Some_Controlled_Type);
                -- (1)
            end P3;
        begin
            P3;
            -- (2)
        end P2;
    begin
        P2;
    end P1;

The discriminant constraint for the allocated object X.all is elaborated by
P3.  The elaboration of this constraint also elaborates the access_definition,
therefore, the master of the anonymous access type is P3.  So when leaving P3,
at the point marked (1), the allocated object X.all.D.all is finalized, as per
7.6.1(11).  But of course, after leaving P3 we can still use the name
X.all.D.all to reference this object (at the point marked (2)), and this is
bad...

It seems to me that we need a rule that says that the master of an anonymous
access type A used in the specification of an access discriminant in type T,
the master of A is the master of T, not the master that caused A to be
elaborated.  Or, alternatively, that when an allocator is used as a
discriminant value for an anonymous access type, the master of the allocated
object is defined to be the master of the discriminated object. (These two
proposals are different, but they both seem to cure the problem, although the
first one is probably easier to implement.)

_____________________________________________________________________
Pascal Leroy                                    +33.1.30.12.09.68
pleroy@rational.com                             +33.1.30.12.09.66 FAX

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

!section 3.10(2)
!subject Anonymous access types and finalization
!reference RM95 3.10(2)
!reference RM95 3.7(27)
!reference RM95 7.6.1(11)
!reference RM95 3.10.2(12)
!reference 96-5633.a Pascal Leroy 96-08-20
!from Tucker Taft 96-08-22
!reference 96-5637.a Tucker Taft 96-8-22>>
!discussion

> The finalization of an object created by an allocator occurs when the master
> of the ultimate ancestor of the access type is left, as stated in 7.6.1(11).
>  In the case of access discriminants, 3.10(2) tells us that "an
> access_definition defines an anonymous access type"; I take this to imply that
> an anonymous access type is an "ultimate ancestor" in the sense of 7.6.1(11).
>  In addition, 3.7(27) tells us that "an access_definition is elaborated when
> the value of a corresponding access discriminant is defined"; I take this to
> imply that the master of the anonymous access type is the master that
> elaborated the discriminant constraint.

Not quite.

The accessibility level determines the lifetime for the purposes of
finalization.  For access discriminants, the accessibility level is
defined by RM95-3.10.2(12):

    The accessibility level of the anonymous access type of an access
    discriminant is the same as that of the containing object or associated
    constrained subtype.

> The interaction of these rules has the unfortunate consequence that it makes
> it possible to reference an object after it has been finalized, as shown in
> the following example:

This is not a problem, given 3.10.2(12).

>     procedure P1 is
>         type T (D : access Some_Controlled_Type) is ... ;
>
>         procedure P2 is
>             type T_Ref is access T;
>
>             X : T_Ref;
>
>             procedure P3 is
>             begin
>                 X := new T (D => new Some_Controlled_Type);
>                 -- (1)
>             end P3;
>         begin
>             P3;
>             -- (2)
>         end P2;
>     begin
>         P2;
>     end P1;
>
> The discriminant constraint for the allocated object X.all is elaborated by
> P3.  The elaboration of this constraint also elaborates the access_definition,
> therefore, the master of the anonymous access type is P3.  So when leaving P3,
> at the point marked (1), the allocated object X.all.D.all is finalized, as per
> 7.6.1(11).  But of course, after leaving P3 we can still use the name
> X.all.D.all to reference this object (at the point marked (2)), and this is
> bad...
>
> It seems to me that we need a rule that says that the master of an anonymous
> access type A used in the specification of an access discriminant in type T,
> the master of A is the master of T, not the master that caused A to be
> elaborated.  Or, alternatively, that when an allocator is used as a
> discriminant value for an anonymous access type, the master of the allocated
> object is defined to be the master of the discriminated object. (These two
> proposals are different, but they both seem to cure the problem, although the
> first one is probably easier to implement.)

As specified by 3.10.2(12), the accessibility level associated with the
nested allocator "new Some_Controlled_Type" is the same as that of
the enclosing object, X.all, which is the same as T_Ref.  The master
is hence P2, and the finalization for the nested allocator should be
performed when exiting P2.

> Pascal Leroy                                    +33.1.30.12.09.68
> pleroy@rational.com                             +33.1.30.12.09.66 FAX

-Tuck

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

!section 3.10(2)
!subject Anonymous access types and finalization
!reference RM95 3.10(2)
!reference RM95 3.7(27)
!reference RM95 7.6.1(11)
!reference RM95 3.10.2(12)
!reference 96-5633.a Pascal Leroy 96-08-20
!from Pascal Leroy 96-08-23
!reference 96-5641.a Pascal Leroy 96-8-23>>
!discussion

> The accessibility level determines the lifetime for the purposes of
> finalization.  For access discriminants, the accessibility level is
> defined by RM95-3.10.2(12):
>
>     The accessibility level of the anonymous access type of an access
>     discriminant is the same as that of the containing object or associated
>     constrained subtype.

I stand corrected.  I had failed to see the significance of accessibility
levels in this case, as explained in 7.6.1(4).  I guess I was reasoning a la
Ada 83 ;-)

Pascal

_____________________________________________________________________
Pascal Leroy                                    +33.1.30.12.09.68
pleroy@rational.com                             +33.1.30.12.09.66 FAX

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

!section 3.10.2(13)
!subject Anonymous allocators and tasks/finalization
!reference RM95 3.10.2(13)
!reference RM95 7.6.1(13)
!reference 96-5633.a Pascal Leroy 96-08-20
!reference 96-5637.a Tucker Taft 96-08-22
!reference 96-5641.a Pascal Leroy 96-08-20
!from Tucker Taft 96-09-10
!reference 96-5698.a Tucker Taft 96-9-10>>
!discussion

In 96-5633.s Pascal Leroy indicated a concern with when the object
created by an "anonymous" allocator (one whose expected type
was the anonymous access type of an access parameter) would
be finalized.  In answer, I pointed to 7.6.1(13) where it identifies
the accessibility level of such allocators as being the same as
that of the execution of the called subprogram.

That seemed to resolve the issue.  However, if you consider
anonymous allocators that create tasks as part of an object
whose size is not known at compile-time, you encounter the
first situation where the master of a task should perhaps be the
execution of a construct "smaller" than a block statement.

It seems that 7.6.1(13) should be augmented to cover the case
of anonymous allocators, which can result in the creation of
"anonymous" objects as part of any subprogram call.  Since these
anonymous objects might be of a limited type (unlike the case
for function returns and array aggregates), we need to consider
task "finalization" as well.  I believe the only consistent
approach is to consider the "master" for such anonymous objects
to be (the execution of) the construct (or construct "part") containing
the subprogram call, and ensure that both task waiting and finalization
occur as part of "leaving" the construct part.  By construct part, I mean
what 7.6.1(13) implicitly identifies as the unit of finalization for temps,
namely, the innermost declarative_item or statement, or in the case of
a compound statement, the part of the compound statement
up to the next statement nested within the compound statement.

Two (rejected0 alternatives are to associate the "anonymous" task with the
innermost enclosing (normal) master of the caller, or with the
execution of the called subprogram.  Neither of these work too well.

    Associating with the enclosing master doesn't work,
    since if the anonymous allocator is inside a loop, one clearly wants
    to recover the storage associated with the anonymous allocator
    before going on to the next loop iteration, even though a loop
    is not a master.

    Associating with the called subprogram
    doesn't work too well, since the call frame for the called
    subprogram doesn't exist at the time when the anonymous task is
    created (and may never exist if one of the later parameter
    evaluations raises an exception).

Also, other temps associated with subprogram call are finalized
immediately after the results of the call are no longer needed, so
it seems that anonymous allocators should operate the same way.

Hence, it seems we need a new notion of "master" to properly
deal with anonymous allocators, or we at least need to *permit*
implementations to wait for tasks created by anonymous allocators
before the enclosing "normal" master completes (I suppose non-real-time
implementations can wait for anything anytime they want, but we care
about real-time-conforming implementations as well).

In any case, 7.6.1(13) should acknowledge this new kind of anonymous
object, namely the anonymous object created by the evaluation
of an "anonymous" allocator.

-Tuck

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

From: Stephen W. Baird
Sent: Thursday, October 30, 2003  11:31 AM

This is a preliminary discussion of AI-162, as per my homework
assignment from the Sydney ARG meeting.

--------------------------------

Allocators of anonymous access types introduce a fairly large amount of
complexity to the dynamic semantics of Ada.

This complexity is in some sense proportional to the product of two sets:
the set of contexts in which an anonymous access type may occur and a set
of issues which must be resolved for each context.

The set of contexts in which an anonymous access type may occur includes
    - as a discriminant type of a "really limited" type
    - as a formal parameter type
    - as a "garden variety" component type (proposed in AI 230)
    - as a function result type (proposed in AI 325)

The set of issues includes
    - determining the point of finalization for the allocated object
    - determining the master of any task parts of the allocated object
    - determining the storage_pool to be used by the allocator
    - determining the point of reclamation for the allocated object's storage
    - determining the accessibility level of the access value

This leads to the question of how best to express the desired rules.

One could distribute them throughout the manual, adding provisions for
each of the listed contexts to (at least) 3.10.2, 7.6.1, 9.3, and 13.11.
This would require lots of changes (each of several lines) with the
resulting logic scattered nearly to the point of unreadability.

One might try to leverage the work that has already been done for
allocators of named access types by using equivalence rules.
In addition to the general objection "Equivalence rules never work", this
approach has a more specific flaw.
It relies on the assumption that for each allocator of an anonymous access
type, there exists a point where a named access type could have been
declared and which would have resulted in the same program behavior if
that named type had been used as the type of the allocator.
In the case of a function result, this assumption does not hold.

I think the best of the available alternatives would be to add an entirely
new section to the manual, perhaps 4.8.1.
The passages mentioned above (3.10.2, 7.6.1, 9.3, and 13.11) would each
contain a one-line reference to the new section.
This seems like overkill for dealing with what seems like an obscure
corner case, but it is better than the alternatives.

Before I go any further with this, I would like to get the opinion of the
ARG as to whether I am on the right track.

P.S.

An_Only_Tangentially_Related_Rant:
  begin

    I believe the utility of anonymously-typed allocators does not justify
their complexity, that allowing them  was a language design
   mistake, and that AIs 230 and 325 (which, ignoring allocators, seem
reasonable to me) increase the impact of this error.

    I recognize, however, that my opinion on this subject is not widely
shared and that an incompatible change in this area is not a realistic
option.

    I also concede that it would seem odd to continue to allow anonymously
typed allocators in some contexts while disallowing them in
    the new contexts proposed  in AIs 230 and 325.

  end An_Only_Tangentially_Related_Rant;

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

From: Tucker Taft
Sent: Thursday, October 30, 2003  12:00 PM

...
> This leads to the question of how best to express the desired rules.
>
> One could distribute them throughout the manual, adding provisions for
> each of the listed contexts to (at least) 3.10.2, 7.6.1, 9.3, and 13.11.
> This would require lots of changes (each of several lines) with the
> resulting logic scattered nearly to the point of unreadability.

So what else is new?  But seriously, we do this for essentially
every construct.  I don't know why anonymous allocators need
"special" treatment.  Programmers just want them to work.
Implementors can deal with scattered definitions.

> ...
> I think the best of the available alternatives would be to add an entirely
> new section to the manual, perhaps 4.8.1.
> The passages mentioned above (3.10.2, 7.6.1, 9.3, and 13.11) would each
> contain a one-line reference to the new section.
> This seems like overkill for dealing with what seems like an obscure
> corner case, but it is better than the alternatives.

I don't agree.  I don't see sufficient benefit to justify
creating a separate section.

> Before I go any further with this, I would like to get the opinion of the
> ARG as to whether I am on the right track.

I would add/fix the wording in the relevant sections.

With your proposal, you will have to add/change/remove some amount
of wording in each of these sections to point to the new
section, plus write the wording in the new section, in each
case creating sufficient context to link the wording back to
the place where it is relevant.

Sounds like a loser to me.

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

From: Randy Brukardt
Sent: Thursday, October 30, 2003  12:03 PM

Steve wrote:

> An_Only_Tangentially_Related_Rant:
>   begin
>
>     I believe the utility of anonymously-typed allocators does not justify
> their complexity, that allowing them  was a language design
>    mistake, and that AIs 230 and 325 (which, ignoring allocators, seem
> reasonable to me) increase the impact of this error.
>
>     I recognize, however, that my opinion on this subject is not widely
> shared and that an incompatible change in this area is not a realistic
> option.

I agree with you 100% here. I think there would be some value to finding out
how often this 'feature' is used. (I've never used it personally.) So far as
I am aware, the support for this feature primarily comes from one very
influental ARG member. If there really was a lot of interest in this, I
think we would have accepted (with modifications), not killed, Mike Kamrad's
version of this AI. (It, after all, is what is needed to solve this problem.
I very much doubt that there is a substantially simpler solution that solves
the problems.)

>     I also concede that it would seem odd to continue to allow anonymously
>     typed allocators in some contexts while disallowing them in
>     the new contexts proposed  in AIs 230 and 325.

There is another possibility which is not context-dependent and helps quite
a bit. That would be to prohibit anonymous allocators if the designated type
"needs finalization". Since we never figured out what that means, I suspect
that using such a thing would get you into trouble on many compilers.
They'll do *something*, but exactly what isn't clear (and it may not be
useful). I think Janus/Ada would generate an internal error because the
finalization chain wouldn't have been set up (although I haven't tried it).

That would limit the incompatibility, and completely eliminate the task
master and finalization issues. Tucker's answer for the storage
pool/reclamation problem seems OK (other than for AI-325, which I hope dies
an early, well-deserved death).

> I think the best of the available alternatives would be to add an entirely
> new section to the manual, perhaps 4.8.1.
> The passages mentioned above (3.10.2, 7.6.1, 9.3, and 13.11) would each
> contain a one-line reference to the new section.
> This seems like overkill for dealing with what seems like an obscure
> corner case, but it is better than the alternatives.

Without seeing wording, it's very hard to say for sure. But I don't really
see the point; I doubt that you could share much between the rules to handle
the various issues. (Especially if we have the guts to solve the messiest
issues by banning them.)
Without such sharing, there is little benefit to having the rules in the
same place. And I don't think that there is much in common between a "task
master" rule and a "storage pool" rule, for example.

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

From: Tucker Taft
Sent: Thursday, October 30, 2003  12:20 PM

I agree that they might have been a mistake, but they
are definitely used now, and the new Ada 2005 uses are the *simpler*
ones, since they are the cases that *do* correspond to
named access types.  The tough ones are the old cases,
in particular access discriminants of limited types.
Of course, those are exactly the cases where they are
most useful, where you couldn't easily write an equivalent
"normal" allocator.

If you use access discriminants, it is not particularly weird
to want to initialize the access discriminant with an allocator.
They allow you to overcome the limitation that you can't have discriminants
of a record or floating point type.

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

From: Tucker Taft
Sent: Thursday, October 30, 2003  12:30 PM

One other point:

> ... If there really was a lot of interest in this, I
> think we would have accepted (with modifications), not killed, Mike Kamrad's
> version of this AI. (It, after all, is what is needed to solve this problem.
> I very much doubt that there is a substantially simpler solution that solves
> the problems.)

The problem is more one of description, since we don't currently
have individual statements as masters.

Implementors already know how to deal with aggregates and function calls
that require finalization, so fitting task waiting into that
can be relatively straightforward.  Chances are implementors who
handle finalization right also do something reasonable with tasks.

One possibility is to continue to ignore the task-waiting
problem in the RM, and put in some kind of "to be honest".
Or write something simple for the semantics, and allow
more options via an implementation permission.

(Without contract model problems, I don't see how to disallow
creating tasks or controlled objects in anonymous allocators.)

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

From: Stephen W. Baird
Sent: Thursday, October 30, 2003  1:45 PM

Tuck wrote:
> I agree that they might have been a mistake, but they
> are definitely used now, and the new Ada 2005 uses are the *simpler*
> ones, since they are the cases that *do* correspond to
> named access types.  The tough ones are the old cases,
> in particular access discriminants of limited types.
> Of course, those are exactly the cases where they are
> most useful, where you couldn't easily write an equivalent
> "normal" allocator.

I disagree. If an allocator of an anonymous type is used as a
discriminant of an limited type, then that is equivalent to declaring
a named access type at the point of the constraint (not at the
point of the limited type declaration) and using that for the type
of the allocator. The exception to this is if the allocator is
being used as a discriminant value to constrain another allocator,
in which case the named access type would have to be declared at the
point of the type of the second allocator (or, if the type of the second
allocator is also anonymous, at the point where its equivalent named
access type might have been declared - we assume inductively that such
a point exists).

The case where you can't write an equivalent normal allocator is
the anonymous function result type case (AI-325). Here, for the first time
(ignoring shared code generics; I haven't thought about them)
we have an allocator whose accessibility level is not known statically.

In particular, if the caller is more nested than the callee, then it
would be very difficult to use a named access type for the type of
the returned allocator.

If it wasn't for this case, one could use equivalence rules in the RM
and I would at least consider doing so.

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

From: Tucker Taft
Sent: Thursday, October 30, 2003  3:29 PM

I would recommend you not worry about AI-325 when working on AI-162.
AI-325 may sink under its own weight anyway, and the worry about
anonymous allocators in return statements is part of its weight,
not AI-162's, at this point.  No need for you to unburden it.

One other point I might mention.  There seems to be a fair
amount of support for AI-287, "Limited Aggregates Allowed."
If we presume that will happen, then we need to deal with
tasks created by aggregates.  This seems very nearly identical
to the problem associated with tasks created by anonymous
allocators passed as a parameter.

It would seem that 7.6.1(13) could be incorporated into the
definition of master in 7.6.1(3), and then finalization and
task waiting would be handled uniformly for statements or
declarations containing such aggregates or anonymous allocators.

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

From: Randy Brukardt
Sent: Thursday, October 30, 2003  7:00 PM

Tucker said:

> I would recommend you not worry about AI-325 when working on AI-162.
> AI-325 may sink under its own weight anyway, and the worry about
> anonymous allocators in return statements is part of its weight,
> not AI-162's, at this point.  No need for you to unburden it.

Right. And technically, since AI-162 is not an amendment AI, it shouldn't
consider AI-230 or any other amendment either.

> One other point I might mention.  There seems to be a fair
> amount of support for AI-287, "Limited Aggregates Allowed."
> If we presume that will happen, then we need to deal with
> tasks created by aggregates.  This seems very nearly identical
> to the problem associated with tasks created by anonymous
> allocators passed as a parameter.

My initial reaction was no, they're only allowed in contexts where they are
directly initializing an object. But that's not what the AI says. And,
worse, there doesn't seem to be any semantics for passing an aggregate to a
parameter of a by-reference type. So I have no idea what is supposed to
happen here. (This would seem to be the case for Ada 95 as well, for tagged
type aggregates.) The problem is that 6.4.1(10) talks about passing a "view
conversion of the actual parameter". But view conversions are only defined
for "the name of an object" 4.6(5/1). There is no object, only a value, for
an aggregate. So what is going on?

In any case, presuming you are right, any such wording belongs to AI-287 (it
would be unfair to stuff it into some other AI with the claim that we have
to do it anyway, because we clearly don't). Which, unfortunately, means
reopening that AI.

Earlier, Tucker said:

>(Without contract model problems, I don't see how to disallow
>creating tasks or controlled objects in anonymous allocators.)

Well, Janus/Ada currently uses an assume-the-worst model: any program with
such an allocator GP faults (which is certainly the worst that could happen
;-). It clearly would be better to reject any such programs.

Seriously, the language could use an assume-the-worst model, but that seems
a bit strong. (Unless you want to ban such allocators altogether.) It also
clearly could be a mixed legality/runtime check like accessibility. But I
realize that's not particularly "clean".

>The problem is more one of description, since we don't currently
>have individual statements as masters.

Right, but we have to have that (in some form) in order to deal with this
problem.

>Implementors already know how to deal with aggregates and function calls
>that require finalization, so fitting task waiting into that
>can be relatively straightforward.  Chances are implementors who
>handle finalization right also do something reasonable with tasks.

I don't think they are the same problem. Certainly some guy named Tucker
thought that they were separate when it came up discussing AI-280: "Hence, I
think this implies that if you have per-object work to do storage
reclamation, finalizing a master requires three separate sequences:
        1) wait for all subtasks
        2) finalize all controlled/protected objects
        3) reclaim all storage
Trying to intermingle any of these three will violate the RM in my view."
(message of Dec. 11, 2001) I don't see a practical RM wording that doesn't
intermingle them.

Similarly, finalization of anonymous access allocators is a completely
separate problem from finalization of aggregates. The issue is that access
types have their own, separate finalization list [as an access type is a
master]. That list has to be initialized somewhere, and that requires
special processing. Similarly for access type task masters. You'd have to do
an extra scan of every statement's expression tree to see if any
finalization chains or masters were needed.

But I think you would prefer that this access "type" does not have the
semantics of an access type at all. (That is, there is no separate
finalization list, pool, and task master). That seems like a disaster; we'd
have to look at every rule that involves tasks or finalization or pools and
insure that they have the right wording to support such a "split".

Implementation-wise, it would be worse: such an item would have to be
represented differently than an access type in the compiler, and we'd have
to check and in many cases do something different everywhere that an access
type is used. A quick check of Janus/Ada's source shows 69 uses of "Is_Ptr",
but the middle pass doesn't even use a special function: it assumes that all
Ptr_Class types (that's a representation) are implemented the same way. I
found over 550 uses of "Ptr_Class". (These counts probably are high, because
of hits of entities that contain those phrases.) To find and change all of
the placed needed to separately support such allocators would be daunting.

In any event, such a model wouldn't completely work for Janus/Ada. For
historical reasons, task masters are separate objects from the finalization
chain (which implements masters in the language sense); they're linked onto
the list at an appropriate place. So, while we might be able to get rid of
the finalization chain for anonymous access types (although that seems
sensible only for anonymous access used as a parameter), getting rid of the
master isn't possible. We'd have to generate a statement level master for
the access type, and that would be a new (and potentially expensive)
construct.

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

From: Tucker Taft
Sent: Thursday, October 30, 2003  8:26 PM

> ...
> > One other point I might mention.  There seems to be a fair
> > amount of support for AI-287, "Limited Aggregates Allowed."
> > If we presume that will happen, then we need to deal with
> > tasks created by aggregates.  This seems very nearly identical
> > to the problem associated with tasks created by anonymous
> > allocators passed as a parameter.
>
> My initial reaction was no, they're only allowed in contexts where they are
> directly initializing an object. But that's not what the AI says. And,
> worse, there doesn't seem to be any semantics for passing an aggregate to a
> parameter of a by-reference type.

What is the confusion here?  An aggregate is a constant object (3.3(21)).
It seems clear what it means to pass it by reference.

> ... So I have no idea what is supposed to
> happen here. (This would seem to be the case for Ada 95 as well, for tagged
> type aggregates.) The problem is that 6.4.1(10) talks about passing a "view
> conversion of the actual parameter". But view conversions are only defined
> for "the name of an object" 4.6(5/1). There is no object, only a value, for
> an aggregate. So what is going on?

Aggregates are objects.  Also, values of a by-reference type have
associated objects (see 6.2(10)).

> In any case, presuming you are right, any such wording belongs to AI-287 (it
> would be unfair to stuff it into some other AI with the claim that we have
> to do it anyway, because we clearly don't). Which, unfortunately, means
> reopening that AI.

I'm not sure we need to do that.  I am suggesting that via AI-162 we
create a general solution, not one tailored to anonymous allocators.
Once we establish a master around a statement or declaration that
can handle temporary tasks, finalizable objects, or short-lived
anonymous access types, it should be relatively easy to deal
with aggregates and anonymous allocators.

> >The problem is more one of description, since we don't currently
> >have individual statements as masters.
>
> Right, but we have to have that (in some form) in order to deal with this
> problem.

And aggregates containing tasks.

> >Implementors already know how to deal with aggregates and function calls
> >that require finalization, so fitting task waiting into that
> >can be relatively straightforward.  Chances are implementors who
> >handle finalization right also do something reasonable with tasks.
>
> I don't think they are the same problem. Certainly some guy named Tucker
> thought that they were separate when it came up discussing AI-280: "Hence, I
> think this implies that if you have per-object work to do storage
> reclamation, finalizing a master requires three separate sequences:
>         1) wait for all subtasks
>         2) finalize all controlled/protected objects
>         3) reclaim all storage
> Trying to intermingle any of these three will violate the RM in my view."
> (message of Dec. 11, 2001) I don't see a practical RM wording that doesn't
> intermingle them.

All very true, but they tend to happen at the same point.
For example, in our compiler, we have a single "master" record
which can have a list of tasks, and a list of cleanup actions.
We first do all the finalization actions, and then we wait for
all the tasks.  If we are also going to reclaim storage at this
point, we do that after the finalization and task waiting.

> Similarly, finalization of anonymous access allocators is a completely
> separate problem from finalization of aggregates. The issue is that access
> types have their own, separate finalization list [as an access type is a
> master].

An access type is not itself a master.  An access type is elaborated
by some master, and that determines the master of the tasks
created by allocators for the access type (9.3(2)).

> ... That list has to be initialized somewhere, and that requires
> special processing. Similarly for access type task masters. You'd have to do
> an extra scan of every statement's expression tree to see if any
> finalization chains or masters were needed.
>
> But I think you would prefer that this access "type" does not have the
> semantics of an access type at all. (That is, there is no separate
> finalization list, pool, and task master). ...

No, I am not saying that.  All I am recommending is that they
all share a single master from a languge description point of view,
which seems appropriate, given the fact that an access type is
not itself a master.  I don't want to get into the business of
telling you how to implement this at a detailed level.

I am simply arguing that many implementors (not all) use
much of the same mechanism for finalization and task waiting,
and we should take advantage of that by having aggregates
and these kinds of short-lived anonymous access types share
the same master.  Obviously you don't have to take advantage
of that.  But we certainly shouldn't require that the finalization
and the task waiting happen at different points.

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

From: Randy Brukardt
Sent: Thursday, October 30, 2003  9:00 PM

Tucker replied to me:

> > But I think you would prefer that this access "type" does not have the
> > semantics of an access type at all. (That is, there is no separate
> > finalization list, pool, and task master). ...
>
> No, I am not saying that.  All I am recommending is that they
> all share a single master from a languge description point of view,
> which seems appropriate, given the fact that an access type is
> not itself a master.  I don't want to get into the business of
> telling you how to implement this at a detailed level.

Feel free. Your model may make more sense. :-)

> I am simply arguing that many implementors (not all) use
> much of the same mechanism for finalization and task waiting,
> and we should take advantage of that by having aggregates
> and these kinds of short-lived anonymous access types share
> the same master.  Obviously you don't have to take advantage
> of that.  But we certainly shouldn't require that the finalization
> and the task waiting happen at different points.

That I certainly can agree with. It would be harder still if the task master
(for waiting) was inconsistently positioned. (We do use the finalization
mechanism for determining the actual point of triggering of waiting, because
that automatically handles exceptions and aborts. But it is a separate
object placed on the finalization chain at the right point. Storage
management for user-accessible objects (not temporaries) will be done the
same way when I get around to changing it.)

My problem is figuring out how to declare a temporary task master object
(which contains the waiting links) at some point where such things are not
expected. Declaring task masters is messy, because we don't want to generate
one unless we're sure we need it (if one is generated, the tasking runtime
is required in the program, which is not true otherwise. And they're
relatively expensive to initialize, as doing so requires a task supervisor
call). Indeed, this is one of the very few places where we diverge from a
pure-one-pass design, and it's because we do that that it becomes messy. But
this is probably not a language issue.

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

From: Tucker Taft
Sent: Monday, December 1, 2003  4:04 PM

>    The distinction between two kinds of masters (tentatively called
>    "unrestricted" and "restricted") is necessary because some places
>    in the manual need to refer to *all* masters (e.g. the discussion in
>    7.6.1(4) of the finalization of a master or the definition in
>    3.10.2(6) of the accessibility level of a master) while other places
>    need to refer only to old-style (i.e. unrestricted) masters (e.g.
> 3.10.2(7)
>    and 9.3(3)). An earlier (uncirculated) version of this AI attempted to
>    simply use the term "master" in both cases, but this didn't work well.

Can you elaborate on this?  It is not ideal to have to make this distinction.
What would happen if we just used "master" everywhere?  There are already
plenty of blocks and subprograms which are called masters but don't really
do anything master-ish.

>    The terms "unrestricted master" and "restricted master" are not themselves
>    very satisfactory. Does anyone have a better alternative?

The best alternative is to eliminate the distinction, or use something
like "a master which is the execution of a block or a program unit"
if there are only a very small number of places we need to make the
distinction.  Do we really ever have to talk about "restricted" masters?
Can't these at least just be "masters"?

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

From: Steven W. Baird
Sent: Tuesday, December 2, 2003  5:30 PM

> Can you elaborate on this?  It is not ideal to have to make this distinction.

I agree with you that it would be nice if we could find a way to avoid
introducing these terms, but I haven't found it yet.

Tuck wrote:
> What would happen if we just used "master" everywhere?

Simply eliminating the distinction between the different kinds of masters
would not work.

For example, if 9.3(3) referred to "master" instead of "unrestricted
master", then the elaboration of the declaration
    X : Some_Task_Type;
would have to deadlock waiting for the (unactivated) task to complete
because the declaration is now a master.

> The best alternative is to eliminate the distinction, or use something like
> "a master which is the execution of a block or a program unit" if there are
> only a very small number of places we need to make the  distinction.  Do we
> really ever have to talk about "restricted" masters?  Can't these at least
> just be "masters"?

Unfortunately, the number of places we need to make the distinction is not
small.

There are several places in the manual that reference masters (3.10.2,
7.6.1, 9.3, 9.8, 10.2) and several that reference
unrestricted masters (3.10.2, 6.5, 7.6.1, 9.3, 13.11).

Restricted masters are referenced only 7.6.1.

It might be possible (albeit awkward) to somehow restructure 7.6.1 to
avoid using the term "restricted master", but it seems
to me that there are too many uses of "unrestricted master" to replace
each of them with something like "a master which is
the execution of a task_body, a block_statement, a subprogram_body, an
entry_body, or an accept_statement".  Even if
we manage a shorter definition, we still don't want to replicate it this
many times.

What do you think?

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

From: Randy Brukardt
Sent: Tuesday, December 2, 2003  5:43 PM

A couple of comments on this AI:

> !wording

You've lost the !recommendation section. Even though it just says "(See
summary.)", it has to be present.

> Replace 7.6.1(13/1 - 13.1/1) with
>
>   A restricted master is a master of any anonymous object created as part of
>   its execution by the evaluation of an allocator of an anonymous access type,
>   a function call, or an aggregate except if the object is
>
>     - created as part of the execution of a more nested restricted master;
>
>     - created as part of a rename declaration which renames any part of the
>       object. In this case, the nearest enclosing unrestricted master of the
>       rename declaration is defined to be a master of the object; the
>       accessibility level of the object is defined to be the accessibility
>       level of this master.
>
>   or
>
>     - created by an allocator of an anonymous access type which is used to
>       initialize an access discriminant of another object. In this case, the
>       masters of the allocated object are defined to be masters of the
>       discriminated object; the accessibility level of the allocated object
>       is defined to be that of the discriminated object.
>       The allocated object is finalized when the discriminated object is
>       finalized. The storage occupied by the allocated object is deallocated
>       when the storage occupied by the discriminated object is deallocated.
>
>   For a statement, the finalization of such an anonymous object occurs
>       - when the statement is completed;
>   or
>       - immediately before beginning the execution of a nested statement;
>   or
>       - in the case of a potentially blocking operation, immediately before
>         the operation blocks the current task
>   , whichever occurs first. Otherwise, this finalization occurs when the
>   entity is completed.

The format of these bulleted lists is a non-starter. You can't put text
between bullets, you can't start lines with punctuation, and you can't have
text following that belongs to the list. None of those things are supported
in HTML, the RM formatting tool, and I believe that they may violate some of
ISO's formatting rules as well. (Not that we follow those too closely.)
Trying to fix this wording is a bigger job than I want to take on right now,
so I'll leave it to the author. A lot of the discussion also does this; it
also needs to be fixed - before this AI gets to editorial review!!

> An implementation must somehow keep track of whether a given
> access discriminant component "belongs" to the enclosing discriminated
> object.

The distributed overhead that this implies seems to be overkill for an
extremely marginal case. Is there anything *useful* that can be accomplished
with this construct that can't be handled with named access types? The need
for anonymous discriminants comes from self-referencing data structures, and
by definition, those don't use allocators for the discriminants!

I'd just say its a bounded error to deallocate one of these things. (Which
implies that you should never create one in the first place, reasonable
advice in any case.)

> Only a non-component object of the discriminated type can "own" the object
> designated by its discriminant. For a non-allocated object, ownership is known
> statically. Thus, distributed overhead need only be incurred in the case of an
> allocated object with one or more access discriminants. One implementation
> strategy would be to prepend this information to the allocated object in
> much the same way that some implementations prepend dope vectors for allocated
> arrays when the designated subtype is unconstrained.

Janus/Ada uses a single set of thunks to allocate/deallocate all objects
(whether they are local or allocated from the heap), with a pool parameter
and passing in the top-level chunk of memory. It wouldn't work to have
different representations in the heap and on the stack. Secondly, we'd have
to have a new kind of descriptor in order to implement the above, which
would mean a new kind of subtype, and extra work to identify that subtype
and do the right thing everywhere in the compiler that an access object can
be referenced. Sounds like a nightmare to me, plus the extra cost of an
extra indirection on every use. The alternative of having a bit with every
access discriminant sounds better on both counts, except that it would
change the representation of any object with an access discriminant (making
such objects incompatible with those from the existing Ada 95
implementation), and could waste as much as 31 bits per discriminant
(because of alignment issues). Can this case really be worth that??

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

From: Tucker Taft
Sent: Tuesday, December 2, 2003  8:09 PM

> The format of these bulleted lists is a non-starter. You can't put text
> between bullets, you can't start lines with punctuation, and you can't have
> text following that belongs to the list.

I think all of this is due to the wording being "wrapped"
by some mail software somewhere.  I suggest Steve just
reformat it with narrower margins and send it again,
or attach it as a ".txt" file rather than including it
in the body of the mail message.

>>An implementation must somehow keep track of whether a given
>>access discriminant component "belongs" to the enclosing discriminated
>>object.
>
> The distributed overhead that this implies seems to be overkill for an
> extremely marginal case. ...

I agree.  We should *allow* implementations to finalize and
deallocate the object created by the anonymous allocator when
the enclosing object is deallocated, but we shouldn't require it.
It should be postponed no later than when the outer access type
is finalized.

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

From: Tucker Taft
Sent: Tuesday, December 2, 2003  8:11 PM

> Simply eliminating the distinction between the different kinds of masters
> would not work.
>
> For example, if 9.3(3) referred to "master" instead of "unrestricted
> master", then the elaboration of the declaration
>     X : Some_Task_Type;
> would have to deadlock waiting for the (unactivated) task to complete
> because the declaration is now a master.

This seems like a problem that is solvable without creating
multiple kinds of masters.  Suppose we change 9.3(3) to say:

   If the task is created by the elaboration of an object_declaration,
   it depends on each master that includes this elaboration{,
   other than the object_declaration itself}.

We also want to be sure that (the elaboration of) a package or protected
unit is never considered a master, since we don't want them to become
masters of tasks or finalizable objects declared within them.

It might be simpler to turn certain expressions into masters,
rather than statements or declarations to avoid this containment
problem.  Then all we need to define is when such expression masters
should be/must be finalized.

> It might be possible (albeit awkward) to somehow restructure 7.6.1 to
> avoid using the term "restricted master", but it seems
> to me that there are too many uses of "unrestricted master" to replace
> each of them with something like "a master which is
> the execution of a task_body, a block_statement, a subprogram_body, an
> entry_body, or an accept_statement".  Even if
> we manage a shorter definition, we still don't want to replicate it this
> many times.

I think if you make expressions into masters, you can just say
"masters" in all these contexts.  And as a fall back,
we have the term "expression masters" and "non-expression masters"
if we really need them (but I don't think we do).

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

From: Robert I. Eachus
Sent: Wednesday, December 3, 2003  1:26 AM

To suggest just some wordsmithing, why not use "master" for the general
case, then "master which is not a statement"
and "master which is a statement" when you need to be specific?

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

From: Pascal Leroy
Sent: Wednesday, December 3, 2003  3:29 AM

> The distributed overhead that this implies seems to be
> overkill for an extremely marginal case. Is there anything
> *useful* that can be accomplished with this construct that
> can't be handled with named access types? The need for
> anonymous discriminants comes from self-referencing data
> structures, and by definition, those don't use allocators for
> the discriminants!

I disagree with Randy and Tuck here.  I see access discriminants as a
way to work-around the fact that discriminants have to be of an
elementary type.  If I use an access discriminant (as opposed to a
discriminant of a named access type) it is _precisely_ because I expect
that the two objects will be strongly tied, and in particular will be
finalized at the same point.  This is an important invariant that should
be maintained.

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

From: Randy Brukardt
Sent: Wednesday, December 3, 2003  9:07 PM

> I think all of this is due to the wording being "wrapped"
> by some mail software somewhere.  I suggest Steve just
> reformat it with narrower margins and send it again,
> or attach it as a ".txt" file rather than including it
> in the body of the mail message.

I thought that originally, and tried to correct it, but the text makes no
sense that way. It's quite clear that Steve intended to start lines with
commas and put hanging "or"s between bullets, and that just isn't allowed.
(There also was a lot of word wrapping problems, but those
I fixed when I filed the AI.)

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


Questions? Ask the ACAA Technical Agent