!standard 03.10.02 (3) 04-12-03 AI95-00162/06 !standard 03.10.02 (7) !standard 03.10.02 (13) !standard 06.05 (18) !standard 07.06.01 (3) !standard 07.06.01 (13) !standard 07.06.01 (13.1) !standard 09.03 (2) !standard 09.03 (3) !standard 13.11.02 (17) !class binding interpretation 96-09-10 !status Amendment 200Y 04-12-03 !status ARG Approved 7-0-2 04-11-21 !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 More entities are defined to be masters in order to handle objects associated with the evaluation of an allocator of an anonymous type, a function call, or an aggregate. The enclosing statement or declaration is typically the master of these anonymous objects. Finalization, task dependence, accessibility levels, and lifetimes for these objects are all defined in terms of this master. Finalization, task dependence, accessibility levels, and lifetimes for other objects remain unchanged. !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 "innermost enclosing master" with "innermost enclosing master other than the declaration itself". Delete the second sentence of 3.10.2(13). In 6.5(18) replace "not deeper than" with "not deeper than or equal to". In 7.6.1(3), replace ".. except in the case of a *master*: the execution of task_body, a block_statement, a subprogram_body, an entry_body, or an accept_statement." with ".. except in the case of a *master*: the execution of a body other than a package_body; the elaboration of a declaration other than the declaration of a package; the execution of an accept_statement, a block_statement, or a simple_statement; or the evaluation of an expression or range that is not part of an enclosing expression, range, or simple_statement." Replace 7.6.1(13/1 - 13.1/1) with The master of an object is the master enclosing its creation whose accessibility level (see 3.10.2) is equal to that of the object. In the case of a potentially blocking operation which is a master, finalization of an (anonymous) object occurs before blocking if the last use of the object occurs before blocking. In particular, for a delay_statement, any finalization occurs before delaying the task. In the case of an expression which is a master, finalization of any (anonymous) objects occurs as the final part of evaluation of the expression. In 9.3(2) replace "each master that includes the elaboration of the declaration of the ultimate ancestor of the given access type" with "each master that includes the elaboration of the declaration of the ultimate ancestor of the given access type other than the declaration itself". In 9.3(3) replace "each master that includes this elaboration" with "each master that includes this elaboration other than the declaration itself". Add at the end of 13.11.2(17). If the object being reclaimed has an access discriminant which designates an object 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 ------------ More entities are defined to be masters. These new masters have no effect on objects declared via a declaration (e.g. they do not affect the accessibility level of such an object), but only act as masters for these objects which previously lacked well-defined masters. 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 because of the change to 3.10.2(7). 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) 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. 9) 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. 10) In this example delay Function_Call_Requiring_Finalization.Field; , the function call is finalized before, not after, the task delays. 11) A compound_statement is not a master. Instead, any expression whose nearest enclosing statement/declaration/pragma/expression is a compound_statement is a master. In this example if Function_Call_Requiring_Finalization.Field then declare X : My_Task_Type; begin null; end; end if; , the if_statement is not a master. Instead, the condition is a master. This means that finalization of any objects associated with the compound_statement occurs before, not after, the execution of any nested statements. Similarly, in this example for I in 1 .. Function_Call_Requiring_Finalization.Field loop Foo; end loop; , the function result is finalized before calling Foo. 12) In this example declare generic package G is X : Some_Task_Type; end G; package I is new G (Function_Call_Requiring_Finalization); Y : Some_Task_Type; begin null; end; , the function result object has a deeper accessibility level than the two tasks (which have the same accessibility level). !corrigendum 3.10.2(3) @drepl The accessibility rules, which prevent dangling references, are written in terms of @i, which reflect the run-time nesting of masters. As explained in 7.6.1, a master is the execution of a @fa, a @fa, a @fa, an @fa, or an @fa. An accessibility level is @i another if it is more deeply nested at run time. For example, an object declared local to a called subprogram has a deeper accessibility level than an object declared local to the calling subprogram. The accessibility rules for access types require that the accessibility level of an object designated by an access value be no deeper than that of the access type. This ensures that the object will live at least as long as the access type, which in turn ensures that the access value cannot later designate an object that no longer exists. The Unchecked_Access attribute may be used to circumvent the accessibility rules. @dby The accessibility rules, which prevent dangling references, are written in terms of @i, which reflect the run-time nesting of masters (see 7.6.1). An accessibility level is @i another if it is more deeply nested at run time. For example, an object declared local to a called subprogram has a deeper accessibility level than an object declared local to the calling subprogram. The accessibility rules for access types require that the accessibility level of an object designated by an access value be no deeper than that of the access type. This ensures that the object will live at least as long as the access type, which in turn ensures that the access value cannot later designate an object that no longer exists. The Unchecked_Access attribute may be used to circumvent the accessibility rules. !corrigendum 3.10.2(7) @drepl @xbullet @dby @xbullet !corrigendum 3.10.2(13) @drepl @xbullet, this is the accessibility level of the execution of the called subprogram.> @dby @xbullet !corrigendum 6.5(18) @drepl @xbullet that denotes an object view whose accessibility level is not deeper than that of the master that elaborated the function body; or> @dby @xbullet that denotes an object view whose accessibility level is not deeper than or equal to that of the master that elaborated the function body; or> !corrigendum 7.6.1(3) @drepl After execution of a construct or entity is complete, it is @i, meaning that execution continues with the next action, as defined for the execution that is taking place. Leaving an execution happens immediately after its completion, except in the case of a master: the execution of a @fa, a @fa, a @fa, an @fa, or an @fa. A master is finalized after it is complete, and before it is left. @dby After execution of a construct or entity is complete, it is @i, meaning that execution continues with the next action, as defined for the execution that is taking place. Leaving an execution happens immediately after its completion, except in the case of a master: the execution of a body other than a @fa; the elaboration of a declaration other than the declaration of a package; the execution of an @fa, a @fa, or a @fa; or the evaluation of an @fa or @fa that is not part of an enclosing @fa, @fa, or @fa. A master is finalized after it is complete, and before it is left. !corrigendum 7.6.1(13) @drepl If the @fa in an @fa, or the actual parameter for a generic formal @b parameter in a @fa, denotes any part of an anonymous object created by a function call, the anonymous object is not finalized until after it is no longer accessible via any name. Otherwise, an anonymous object created by a function call or by an @fa is finalized no later than the end of the innermost enclosing @fa or @fa; if that is a @fa, the object is finalized before starting the execution of any @fa within the @fa. @dby The master of an object is the master enclosing its creation whose accessibility level (see 3.10.2) is equal to that of the object. !corrigendum 7.6.1(13.1) @drepl If a transfer of control or raising of an exception occurs prior to performing a finalization of an anonymous object, the anonymous object is finalized as part of the finalizations due to be performed for the object's innermost enclosing master. @dby In the case of a potentially blocking operation which is a master, finalization of an (anonymous) object occurs before blocking if the last use of the object occurs before blocking. In particular, for a @fa, any finalization occurs before delaying the task. In the case of an @fa which is a master, finalization of any (anonymous) objects occurs as the final part of evaluation of the @fa. !corrigendum 9.3(2) @drepl @xbullet for a given access type, it depends on each master that includes the elaboration of the declaration of the ultimate ancestor of the given access type.> @dby @xbullet for a given access type, it depends on each master that includes the elaboration of the declaration of the ultimate ancestor of the given access type other than the declaration itself.> !corrigendum 9.3(3) @drepl @xbullet, it depends on each master that includes this elaboration.> @dby @xbullet, it depends on each master that includes this elaboration other than the declaration itself.> !corrigendum 13.11.2(17) @drepl For a standard storage pool, Free should actually reclaim the storage. @dby For a standard storage pool, Free should actually reclaim the storage. If the object being reclaimed has an access discriminant which designates an object which was created by an allocator of the (anonymous) type of the access discriminant, then the designated object should also be reclaimed. !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.) **************************************************************** From: Tucker Taft Sent: Wednesday, July 28, 2004 10:17 PM Here is an attempt to simplify the wording of AI-162. This AI attempts to refine the definition of "master" so that anonymous objects created by function calls, aggregates, and allocators, get finalized/waited-for sooner rather than later. [This is version /05 of the AI - ED] ****************************************************************