Version 1.6 of ai05s/ai05-0190-1.txt
!standard 13.11.3(0) 10-10-25 AI05-0190-1/05
!class amendment 09-11-03
!status work item 09-11-03
!status received 09-11-03
!priority Low
!difficulty Easy
!subject Global storage pool controls
!summary
Provide a means to force the use of user-defined pools, and a means to specify a
particular pool to be used by default.
!problem
Some applications need to keep tight control over heap allocation. For example,
it is common for object-oriented applications to have many access-to-class-wide
types, but few used for allocation. Another example is an embedded system for
which is is inappropriate to rely on the implementation-provided pools.
This can be done by appplying "Storage_Size use 0" to all types that should not
have allocators, and explicitly specifying a Storage_Pool for the few others.
But this is error prone; one might forget the "Storage_Size use 0".
Another problem is that it is technically erroneous to deallocate from the
"wrong" pool. But it is implementation defined which pool is used for each
access type! Many Ada programmers don't know about this rule, and most Ada
implementations use a single global heap by default, so it is common to write
code that does "new" for one type, converts to another type, and does
Unchecked_Deallocation. This common coding practise could be erroneous on some
implementations. It is uncomfortable for erroneousness to be implementation
defined in this way.
!proposal
(see summary)
!wording
Renumber section 13.11.3, "Pragma Controlled" to be 13.11.4.
Add a new section:
13.11.3 Default Storage Pools
Syntax
The form of a pragma Default_Storage_Pool is as follows:
pragma Default_Storage_Pool(storage_pool_indicator);
storage_pool_indicator ::= storage_pool_name | null
A pragma Default_Storage_Pool is allowed immediately within the visible part of
a package_specification, immediately within a declarative_part, or as a
configuration pragma.
Name Resolution Rules
The storage_pool_name is expected to be of type Root_Storage_Pool'Class.
Legality Rules
The storage_pool_name shall denote a variable.
If the pragma is used as a configuration pragma, the storage_pool_indicator
shall be null, and it defines the "default pool" to be null within all
applicable compilation units (see 10.1.5), except within the immediate scope of
another pragma Default_Storage_Pool. Otherwise, Redundant[the pragma occurs
immediately within a sequence of declarations], and it defines the default pool
within the immediate scope of the pragma to be either null or the pool denoted
by the storage_pool_name, except within the immediate scope of another pragma
Default_Storage_Pool. Redundant[Thus, an inner pragma overrides an outer one.]
The pragma applies to all nonderived access types declared in the places
defined here, including within an instance of a generic unit.
A pragma Default_Storage_Pool shall not be used as a configuration pragma within
the immediate scope of another such pragma.
AARM Rationale: This is to prevent confusion in cases like this:
package Parent is
pragma Default_Storage_Pool(...);
...
end Parent;
pragma Default_Storage_Pool(...); --
package Parent.Child is
...
end Parent.Child;
where the Default_Storage_Pool on Parent.Child would not (if it were legal)
override the one in Parent.
Static Semantics
For an access type to which neither Storage_Pool nor Storage_Size clause
applies:
If the default pool is null, the Storage_Size attribute is defined
by the language to be zero.
Redundant[Therefore, an allocator for such a type is illegal.]
However, this does not apply to access parameters (see Implementation
Advice below).
If the default pool is a pool, the Storage_Pool attribute is that
pool.
Otherwise, Redundant[there is no default pool]; the Storage_Pool
attribute is implementation defined.
AARM Ramification: Default_Storage_Pool is the only way to specify the storage
pool for an anonymous access type.
Implementation Advice
If the default pool is null, then an allocator passed to an access parameter
should be allocated on the stack, and automatically reclaimed.
!discussion
Expected usage scenarios are:
- Default_Storage_Pool(null) as a configuration pragma applying to the whole
program.
To use an allocator for an access type, you have to apply a Storage_Pool
or Storage_Size pragma to that type, or else put a Default_Storage_Pool
pragma such that the type is within the pragma's immediate scope.
- Default_Storage_Pool in the spec of the root package of a program or
subsystem.
This default pool is used within the package and its children,
but you can override it with a Storage_Pool or Storage_Size clause,
or with another Default_Storage_Pool.
!example
!ACATS test
ACATS B and C tests are needed.
!appendix
From: Bob Duff
Sent: Thursday, November 19, 2009 12:52 PM
New version of AI05-0190-1. [This is version /02 of this AI. - ED]
The previous version used a Restriction to specify "no allocators allowed by
default". I added pragma Default_Storage_Pool to specify a pool to be used by
default. Having done that, it seemed better to use a pragma
No_Default_Storage_Pool instead of that Restriction.
I changed the !subject accordingly.
****************************************************************
From: Bob Duff
Sent: Sunday, November 22, 2009 1:16 PM
I wrote:
> Provide a means to force the use of user-defined pools, and a means to
> specify a particular pool to be used by default.
One interesting question, which comes from a private conversation I had with
Laurent Guerby, is:
What happens if pragma Default_Storage_Spool applies to an instantiation?
Does it apply to access types in the instance?
If so, does that provide a way to control the storage pool used for instances of
Ada.Constainers.Vectors and friends?
****************************************************************
From: Randy Brukardt
Sent: Tuesday, November 24, 2009 12:28 AM
It's clear that No_Default_Storage_Pool had better apply to instances, so that
any allocators in the generic units are detected. So I suppose from that it
implies that a similar capability exists in the other direction.
But such a capability would be next to useless. There is not (and better not be,
IMHO) any requirements on how the containers packages use the (default) storage
pool (except of course for the bounded forms, where using a pool at all is
disallowed). In particular, a container can request storage from the pool with
any size and any alignment, and as part of any operation. I suppose a program
tied to a particular implementation of the containers might be able to gain some
advantage from a custom pool, but such code is unlikely to be portable.
Note that this is true of any generic that allocates from the default pool.
Unless you are willing to "break privacy" by looking into the body of the
generic, you can't assume anything about how the pool will be called.
But almost all interesting user-defined storage pools make some assumptions
about the alignment and size that will be requested. Such pools would not work
on instances. It would of course be possible to create a monitoring pool that
passes the actual allocation requests to the default pool, but that is not going
to be very helpful in managing storage use.
****************************************************************
From: Randy Brukardt
Sent: Tuesday, November 24, 2009 12:52 AM
> !wording
Minor issue - you didn't recommend some place to put this new wording. It can't
be floating in space!!
The old positioning of D.7(8) related specifically to it being a restriction, so
that doesn't work. So some other suggestion is needed.
****************************************************************
From: Bob Duff
Sent: Tuesday, November 24, 2009 7:01 AM
Right. I intended to suggest someplace in chap 13, but I forgot.
So I'd say it should be 13-dot-<rolldice>.
I'm presuming there was no deliberate intent to max this "optional annex" --
it's just where the Restrictions happen to live.
Actually, I guess it should be 13.11.3, "Default Storage Pools", and bump
"Pragma Controlled" up to 13.11.4.
What about the proposal overall? Do people like it?
It's radically different from the previous version.
****************************************************************
From: Tucker Taft
Sent: Tuesday, November 24, 2009 7:36 AM
> But such a capability would be next to useless.
I don't agree this would be next to useless for a container instance. Clearly
you would have to provide a general purpose storage pool if you are going to
apply it by default to your entire system. But often all that is wanted is an
ability to replace the system-defined default storage pool with a
project-specific default storage pool.
Unlike your experience, most of the storage pools I create are general purpose
in the sense that they handle any size or alignment, but are more specific in
when/if any automatic reclamation occurs.
****************************************************************
From: Randy Brukardt
Sent: Tuesday, November 24, 2009 5:45 PM
> I don't agree this would be next to useless for a container instance.
> Clearly you would have to provide a general purpose storage pool if
> you are going to apply it by default to your entire system. But often
> all that is wanted is an ability to replace the system-defined default
> storage pool with a project-specific default storage pool.
Not sure that makes much sense (unless your vendor's pool is particularly bad,
or if you need additional monitoring capabilities).
> Unlike your experience, most of the storage pools I create are general
> purpose in the sense that they handle any size or alignment, but are
> more specific in when/if any automatic reclamation occurs.
Fine, but those aren't very necessary with the containers, which already are
handling storage management (they're required not to leak, after all). So you
are essentially repeating the existing storage management. Moreover, the
containers (other than the bounded ones) are using controlled objects, and
freeing storage in that case makes your program erroneous (and might very well
cause it to crash hard, depending on how controlled objects are implemented).
The only time it is safe to free storage is if the instance has gone away, and
in that case, the "no leak" rule means that the storage has already been freed.
So while I could see that this capability might be useful with your own
generics, it wouldn't have any real value with the unbounded/indefinite
containers.
One could imagine trying to constrain implementation choices for the unbounded
containers so that pools could be useful (for instance, we'd have to ban reusing
nodes in a different object after they're deleted -- a technique I was planning
to use). But that comes uncomfortably close to requiring the containers to have
a particular body, something that was previously rejected.
Anyway, I still think this capability would necessarily have to be provided in
Bob's proposal (it doesn't make sense otherwise), but I don't think it should be
suggested that it has any particular utility for the language-defined packages.
****************************************************************
From: Tucker Taft
Sent: Tuesday, November 24, 2009 7:39 AM
I don't understand why No_Default_Storage_Pool is made into its own pragma,
rather than relying on a restriction. All you said was:
... it seemed better to use a pragma
No_Default_Storage_Pool instead of that Restriction.
Could you provide a bit more rationale? It smells and feels like a restriction,
so I don't see the advantage of not using the Restrictions pragma.
I also find the wording for No_Default_Storage_Pool a bit confusing, as it says
that an access type that
... has neither a specified Storage_Pool nor Storage_Size ...
ends up with Storage_Size 0. But it isn't clear from the pragma
Default_Storage_Pool that this has the effect of giving an access type a
*specified* Storage_Pool. I think from a language point of view it is still
unspecified, but it picks up the Default_Storage_Pool precisely because it is
unspecified. Hence I would recommend that you be more explicit and say that any
access type that is "not within the scope of a Default_Storage_Pool nor has a
specified Storage_Pool nor a specified Storage_Size..." ends up with a
Storage_Size of zero.
It is somewhat annoying that Default_Storage_Pool can't be a configuration
pragma, but I understand the problem. It would be nice if you could just
specify it once and have it apply "everywhere." I suppose if you organize your
library units into a small number of large subsystems, then you would only need
one per top-level library package. For example, in the RTS you would only need
it in the specs for Ada, Interfaces, and System. Of course creating a storage
pool object that can live in a Pure package would be a bit of a challenge!
Especially if it has to depend on System.Storage_Pools... ;-)
****************************************************************
From: Randy Brukardt
Sent: Tuesday, November 24, 2009 5:50 PM
> I don't understand why No_Default_Storage_Pool is made into its own
> pragma, rather than relying on a restriction.
Bob might have a better reason, but to me it seems that
"No_Default_Storage_Pool" is just another form of specifying the default storage
pool. Perhaps it would be better to just use one pragma:
pragma Default_Storage_Pool (null);
to mean no default pool, although that would require rather annoying resolution
and legality rules.
In that sense it isn't really a restriction, it is just the absence of a default
storage pool. (Every access type must have a pool before an allocator can be
used - Storage_Size = 0 could be thought of as the same as not having a pool. I
assume Bob worded it to the other way to minimize the wording changes needed.)
****************************************************************
From: Tucker Taft
Sent: Tuesday, November 24, 2009 7:17 PM
I don't think this accomplishes what Bob (and I want).
You want this to be a configuration pragma, and probably be one of those that
you give all by itself in a file, so it applies to the whole library. That's why
I think it makes the most sense as a restriction.
And yes, this is for cases where you want to monitor and/or control *all*
allocation, because you don't trust or you haven't certified the vendor-provided
default storage pool, or you simply have special requirements relating to
storage management in the given target environment.
****************************************************************
From: Randy Brukardt
Sent: Wednesday, November 25, 2009 12:38 PM
I was assuming that this would be a configuration pragma. I'm not sure why you
assumed otherwise. Of course, you couldn't give an actual pool in that case
because none would be visible. But there would be no problem with giving "null"
in that usage. So only one pragma is needed, and there is no need to worry about
what happens when they conflict.
****************************************************************
From: Bob Duff
Sent: Wednesday, February 24, 2010 2:01 PM
New version of AI05-0190-1.
In the phone meeting, we said we didn't like allowing multiple
Default_Storage_Pools immediately within a single sequence of declarations, and
we said they should be at the start. But that seems overly restrictive, and I
don't think the wording is that hard. Seems like we want to allow:
with ...;
package P is
My_Pool : ...;
pragma Default_Storage_Pool(My_Pool);
type T1 is access...;
type T2 is access...;
end P;
And it seems harmless and useful to allow:
with ...;
package P is
My_Pool : ...;
pragma Default_Storage_Pool(My_Pool);
type T1 is access...;
Other_Pool : ...;
pragma Default_Storage_Pool(Other_Pool);
type T2 is access...;
end P;
So I wrote this wording:
If the pragma occurs immediately within a package_specification or
declarative_part, it shall precede all declarative_items (other than other
pragmas).
Only one such pragma is allowed immediately within a given sequence
of declarations.
and then deleted it. I'm including it here for the record.
The new wording and discussion sections are below; the rest of the AI remains
unchanged. [This is version /03 of the AI - Editor.]
P.S. I hate the way configuration pragmas work!
****************************************************************
From: Randy Brukardt
Sent: Wednesday, February 24, 2010 6:45 PM
I don't see any wording in here defining where these pragmas apply. You seem to
assume that it is scoped like Suppress, but that doesn't happen automatically;
there has to be some words like 11.5(7.1-2/2) somewhere.
****************************************************************
From: Randy Brukardt
Sent: Wednesday, February 24, 2010 6:52 PM
Sorry. Found it in the Legality Rules. It was formatted as all one long line,
and didn't see that it was out to character 700. It seems weird to have it in
Legality Rules (it's a definition and seems to belong in Static Semantics), but
that isn't a huge issue.
****************************************************************
From: Bob Duff
Sent: Wednesday, February 24, 2010 7:00 PM
> I don't see any wording in here defining where these pragmas apply.
> You seem to assume that it is scoped like Suppress, but that doesn't
> happen automatically; there has to be some words like 11.5(7.1-2/2) somewhere.
If it's a configuration pragma, the general rules about those work.
If it's in a package spec or decl_part, then it says "within the pragma's
immediate scope", except within an inner one. Isn't that sufficient?
****************************************************************
From: Bob Duff
Sent: Wednesday, February 24, 2010 8:08 PM
> Found it in the Legality Rules. It was formatted as all one long line,
> and didn't see that it was out to character 700.
That's odd. I don't use an editor that does that sort of thing.
Maybe it was garbled on your end? I don't normally produce texts with
700-character-long lines!
>... It seems weird to have it in
> Legality Rules (it's a definition and seems to belong in Static
>Semantics), but that isn't a huge issue.
Not sure why I put in in Legality Rules -- probably just a mistake.
It's pretty messy, anyway. I wish we just had some global way to say such
things...
****************************************************************
From: Randy Brukardt
Sent: Wednesday, February 24, 2010 8:59 PM
> That's odd. I don't use an editor that does that sort of thing.
> Maybe it was garbled on your end? I don't normally produce texts with
> 700-character-long lines!
Outlook 2003 seems to remove single line breaks when cutting-and-pasting. So you
might have sent something friendly, but it might have turned into a mess by the
time I filed it.
> >... It seems weird to have it in
> > Legality Rules (it's a definition and seems to belong in Static
> >Semantics), but that isn't a huge issue.
>
> Not sure why I put in in Legality Rules -- probably just a mistake.
> It's pretty messy, anyway. I wish we just had some global way to say
> such things...
Probably because the last Legality Rule depends on it slightly.
****************************************************************
From: Bob Duff
Sent: Friday, October 22, 2010 11:15 AM
Here's a new version of AI05-0190-1, Global storage pool controls.
It addresses all of the points from the Valencia minutes, except one, which I
will discuss separately.
[This is version /04 of the AI - RLB.]
****************************************************************
From: Bob Duff
Sent: Friday, October 22, 2010 11:26 AM
The one point I didn't address is this one:
Tucker makes the claim that access parameters should also be excepted from
the rules about the default pool. Randy says that the point was to get user
control over all allocators. Tucker claims that these items are on the
stack, and should never be allocated anywhere else. Randy says that is not
what Franco and others wanted; they want all allocators to come from a
particular pool so that the values can be moved to other types with longer
lifetimes. Randy thinks that writing stack allocation as an allocator is
misleading to readers anyway -- if you want stack allocation, declare an
object! Not surprisingly, Tucker strongly disagrees and no consensus is
reached.
I have mixed feelings about this. On the one hand, programmers expect "new" to
do heap allocation (possibly in a user-defined pool). On the other hand, the
stack allocation idea is kind of cool, efficiency-wise. On the gripping hand,
not all compilers do it. GNAT, for example, leaks memory for this:
procedure Alloc is
procedure P (X : access String) is
begin
null;
end P;
begin
for X in Integer range 100_000 .. 200_000 loop
P (new String'(1..X => 'x'));
end loop;
end Alloc;
(So badly that it took 5 minutes to notice the Control-C when I tried to kill
the program. Linux has a bad habit of spending a lot of time paging to the disk
instead of paying attention to my keyboard.)
In any case, storage management is generally "Implementation Advice", so we
can't really force compilers to do anything in particular.
I think it would be a Bad Thing if this contentious issue sinks the AI. The
fact is, passing alligators to access parameters is a rare thing. I badly want
the ability to specify a default, and I'm willing to go either way on the issue
of access parameters. I'll just note that if the pragma doesn't apply to access
parameters, then it doesn't accomplish the goal of preventing use of the
system-provided heap. OTOH, it accomplishes 99% of that goal, which is good
enough for me.
Please discuss.
****************************************************************
From: Tucker Taft
Sent: Friday, October 22, 2010 11:55 AM
Why not just say that in the presence of pragma Default_Storage_Pool(null),
allocators for access parameters are required to be allocated on the (secondary)
stack (or equivalently, automatically reclaimed at the end of the
statement/declaration containing the call).
It really is pretty trivial to
implement this "optimization" and the intent of this feature was that it would
work this way. This is indicated by AARM 13.1(25.a/2):
Implementation Note: {AI95-00230-01} For access
parameters and access discriminants, the "storage pool"
for an anonymous access type would not normally
exist as a separate entity. Instead, the designated
object of the allocator would be allocated, in the
case of an access parameter, as a local aliased
variable at the call site, and in the case of an
access discriminant, contiguous with the object
containing the discriminant. This is similar to
the way storage for aggregates is typically managed.
We shouldn't penalize all users because some implementations haven't implemented
the features as it was intended. If an implementation wants to support this
pragma, then they will have to implement this "optimization" now.
This seems essentially equivalent to supporting:
Blah & Integer'Image(F(X))
as a parameter. You aren't supposed to leak storage.
****************************************************************
From: Bob Duff
Sent: Monday, October 25, 2010 1:20 PM
> Why not just say that in the presence of pragma
> Default_Storage_Pool(null), allocators for access parameters are
> required to be allocated on the (secondary) stack (or equivalently,
> automatically reclaimed at the end of the statement/declaration
> containing the call).
OK. New version included below.
[This is version /05 - Editor.]
****************************************************************
From: Randy Brukardt
Sent: Monday, October 25, 2010 2:11 PM
> Why not just say that in the presence of pragma
> Default_Storage_Pool(null), allocators for access parameters are
> required to be allocated on the (secondary) stack (or equivalently,
> automatically reclaimed at the end of the statement/declaration
> containing the call).
This means the pragma is a lie in this case, because we are still allowing an
allocator from *a* default pool. The fact that the pool is somehow "safer" than
the normal default pool is irrelevant.
I realize that you can get the needed effect by using both
No_Anonymous_Allocators (from AI05-0152-1) and Default_Storage_Pool(null), but
you would have to use No_Anonymous_Allocators over the entire program.
> It really is pretty trivial to
> implement this "optimization" and the intent of this feature was that
> it would work this way.
You mean your intent. I doubt that there was ever any concurrence from any
technical group; it's just an AARM note that no one complained about enough to
get it removed. (And I know I have complained about it in the past.)
This is a significant misfeature; the only saving grace is that most compilers
have not implemented it so little code uses it. It simply advertises to do
something that it does not do; most users would be amazed to find out that the
object has such a short lifetime. In the rare cases where you want an allocator
in a call, you're going to want it to live much longer than the call itself
(typically it is creating an object and passing it to a registration routine).
Anonymous access parameters should be even less likely in Ada 2012, now that "in
out" can be used on functions. So readers will be very surprised in the rare
case when they encounter it.
Moreover, it is trivial to write this in the unlikely event that you actually
need it:
declare
Temp_Object : aliased Some_Type;
begin
A_Call (Temp_Object'Access, ...);
end;
is far clearer than
A_Call (new Some_Type, ...);
because the latter implies a different lifetime for the allocator.
...
> We shouldn't penalize all users because some implementations haven't
> implemented the features as it was intended.
We shouldn't penalize all readers of Ada programs because somebody thought of a
neat trick which is completely contrary to the expectations of the readers. I
think anyone that uses this trick in a program deserves a time-out from Ada
programming...
If you say Default_Storage_Pool(null), you don't want any allocators from any
default pool. Period. Making a hole for a rarely-used trick that no one should
be using in the first place is sheer idiocy.
Randy.
P.S. Bob says:
>I think it would be a Bad Thing if this contentious issue sinks the AI.
True. But...
>The fact is, passing alligators to access parameters is a rare thing.
It should never happen at all. It's a hacky trick that no sane Ada programmer
ought to use.
>I badly want the ability to specify a default, and I'm willing to go
>either way on the issue of access parameters.
I'm not. The Tucker-friendly version of this proposal is worse than having none
at all. I vote "no" on your current draft. Sorry.
****************************************************************
From: Bob Duff
Sent: Monday, October 25, 2010 2:53 PM
> It should never happen at all. It's a hacky trick that no sane Ada
> programmer ought to use.
> I'm not. The Tucker-friendly version of this proposal is worse than
> having none at all. I vote "no" on your current draft. Sorry.
The above two paragraphs contradict each other!
****************************************************************
From: Robert Dewar
Sent: Monday, October 25, 2010 3:06 PM
I am entirely sympathetic to Randy's position here, I find it plain odd for an
access value to deallocate automatically like this. Just not Ada at all, and
VERY unexpected, even worse if it is an optimiation that implementations might
or might not do. And I really don't see where a controlled value would be
finalized in this scenario. Very confusing.
****************************************************************
From: Tucker Taft
Sent: Monday, October 25, 2010 3:14 PM
I didn't just make this up. The wording is in the AARM that explains this
intent, and it has been there for over 15 years. All compilers with AdaMagic
front ends (Aonix, Green Hills, Patriot Missile, ADI SHARC) have provided this
behavior since the beginning. I can't believe I am the only Ada programmer in
the world who has taken advantage of this functionality.
****************************************************************
From: Robert Dewar
Sent: Monday, October 25, 2010 3:15 PM
> The above two paragraphs contradict each other!
I don't see the contradiction at all.
****************************************************************
From: Robert Dewar
Sent: Monday, October 25, 2010 3:18 PM
> I didn't just make this up. The wording is in the AARM that explains
> this intent, and it has been there for over 15 years. All compilers
> with AdaMagic front ends (Aonix, Green Hills, Patriot Missile, ADI
> SHARC) have provided this behavior since the beginning. I can't
> believe I am the only Ada programmer in the world who has taken
> advantage of this functionality.
AARM wording never creates requirements or even implementation advice. GNAT has
never done this "optimization", and I don't particularly like the idea of doing
it. So not only do we have a situation with a feature that is controversial, but
it is also, right now, creating non-portability problems.
It is of course not surprising that AdaMagic does this optimization given the
same author of the AARM and the compiler, but as I say, AARM language never
creates requirements or impl advice, and in this case the optimization does not
seem neutral to me, it seems easy to imagine cases where the limited life time
would cause unexpected results.
****************************************************************
From: Bob Duff
Sent: Monday, October 25, 2010 3:57 PM
> > The above two paragraphs contradict each other!
>
> I don't see the contradiction at all.
It seems that everyone, including Randy, believes that pragma
Default_Storage_Pool is an important feature. Certainly I do.
But Randy seems to be saying:
Passing an allocator to an access parameter is
unimportant, nobody should even use this feature.
Passing an allocator to an access parameter is
important -- it has important semantic
interactions with Default_Storage_Pool.
That's a contradiction. The semantics of passing an allocator to an access
parameter cannot be both important and unimportant.
If you never use feature X, then you can happily use Default_Storage_Pool
without caring about interactions with feature X -- no matter what X is, nor
what the interactions are.
****************************************************************
From: Bob Duff
Sent: Monday, October 25, 2010 4:03 PM
> requirements or impl advice, and in this case the optimization does
> not seem neutral to me, it seems easy to imagine cases where the
> limited life time would cause unexpected results.
I'm remaining neutral on this sub-argument between Tucker and Randy. I see both
sides -- so they're probably both upset with me. ;-)
But I don't agree with your "easy to imagine..." above. Try constructing an
example that illustrates the point. And make sure you compile it, because
otherwise, I won't know if it runs afoul of accessibility rules. ;-) Actually,
you have to run it, too, since some of the relevant accessibility rules are
run-time checks!
Note that there is no suggestion to change where anything gets finalized, so
controlled types are a red herring. The existing finalization rules are
compatible with both Tucker's and Randy's view.
****************************************************************
From: Robert Dewar
Sent: Monday, October 25, 2010 4:04 PM
> But Randy seems to be saying:
>
> Passing an allocator to an access parameter is
> unimportant, nobody should even use this feature.
>
> Passing an allocator to an access parameter is
> important -- it has important semantic
> interactions with Default_Storage_Pool.
No, you mispresent him, let me restate, and consider it to be what Robert is
saying:
>> Passing an allocator to an access parameter is
>> not a critical feature, and not likely to be
>> used often.
>> But if it is used, it should behave in a normal
>> manner, like any other allocator, and not be subject
>> to some strange allowed optimization behavior that
>> may make it have a shorter life time than expeted.
****************************************************************
From: Bob Duff
Sent: Monday, October 25, 2010 4:12 PM
It's reasonable for Randy (and you) to believe that a non-critical feature
should behave in a certain way.
It's not reasonable to say that the semantics of a non-critical feature are
critical.
The point is: he says he's against having Default_Storage_Pool at all, unless he
gets his way on the semantics of this other, supposedly non-critical, feature.
****************************************************************
From: Bob Duff
Sent: Monday, October 25, 2010 4:17 PM
>> But if it is used, it should behave in a normal
>> manner, like any other allocator, and not be subject
>> to some strange allowed optimization behavior that
>> may make it have a shorter life time than expeted.
Anyway, I want to see your example that illustrates how such a "shorter life
time" can be visible.
Also, I think you're misrepresenting Tucker's position a bit:
He's not proposing any new "strange optimization" -- the "optimization" is
already clearly allowed in Ada 95, and it would probably be REQUIRED if we had
some formal way to talk about storage leaks.
****************************************************************
From: Randy Brukardt
Sent: Monday, October 25, 2010 4:24 PM
> Passing an allocator to an access parameter is
> unimportant, nobody should even use this feature.
>
> Passing an allocator to an access parameter is
> important -- it has important semantic
> interactions with Default_Storage_Pool.
>
> That's a contradiction. The semantics of passing an allocator to an
> access parameter cannot be both important and unimportant.
I understand your point, but you are taking these statements out of context.
We're talking about the behavior of Default_Storage_Pool(null), not this
(mis)feature in general. What Tucker's suggestion does is takes a very simple
rule (no use of default storage pools) and adds a bunch of verbiage to it to
create an exception to the rules. For a case that shouldn't happen in the first
place, so there can be no value to making a hole in the rules. And that hole can
cause trouble in the case that someone accidentally writes such an allocator
(which is *exactly* what this pragma is about detecting - accidental use of
allocators that don't have defined storage pools). So this exception does damage
to the intended purpose of the pragma. Is this going to happen very often? No,
of course not. But then again, neither is any other allocator that would be
detected by this pragma (by definition, a program using this pragma isn't going
to contain such allocators on purpose).
When you tell Robert that it is very difficult to find a legitimate use of an
allocator in this context with a longer lifetime, I surely agree. The problem
really is that people are unlikely to realize all of those things that will fail
and very well may write such an allocator thinking it does what it appears to do
(as opposed to what it *does* do). And that use will compile correctly but not
work. And of course, if that happens in some rarely used code, you may simply
not detect it until the system is fielded. Yuck.
****************************************************************
From: Robert Dewar
Sent: Monday, October 25, 2010 4:36 PM
> Try constructing an example that illustrates the point.
Suppose the access type is converted to type address using UC, and passed to a C
routine that registers it for some reason or other.
Note that "easy to imagine" /= "expected to occur frequently in practice" !
> Note that there is no suggestion to change where anything gets
> finalized, so controlled types are a red herring.
> The existing finalization rules are compatible with both Tucker's and
> Randy's view.
OK, I guess I don't know enough, because that does not make sense to me, but I
am sure that's my fault! :-) Although I note that our expert on controlled types
is also confused, do perhaps you had better state the rule :-)
****************************************************************
From: Robert Dewar
Sent: Monday, October 25, 2010 4:42 PM
> It's reasonable for Randy (and you) to believe that a non-critical
> feature should behave in a certain way.
>
> It's not reasonable to say that the semantics of a non-critical
> feature are critical.
Well OK, I guess what's wrong here is making the judgment of what is or what is
not critical. Only the programmer knows what is critical. You can make the
judgment that something will be seldom used, but you can't know if that unusual
usage is or is not critical.
> The point is: he says he's against having Default_Storage_Pool at all,
> unless he gets his way on the semantics of this other, supposedly
> non-critical, feature.
Well that kind of "if I can't have this little feature, I will reject the big
feature, even if the little feature is something we all agree is a corner case"
approach is not easy to deal with. But I don't see changing my opinion on that
basis.
Probably the best compromise is to have permission to allocate on the stack. I
could settle for that I suppose, even though it's a bit odd.
Actually being able to allocate things on the local stack is a generally useful
feature, I would not mind this being more generally available, as it is in
Algol68
x := heap int; allocates integer on the heap
x := loc int; allocates integer on the stack.
Actually if you try to make a *requirement* to allocate on the stack you can't
(the notion of stack is not part of the formal model of Ada which deals only in
lifetimes). All you can say is that the lifetime is bla, but that does not force
implementations to deallocate. As I noted in an earlier message, even UC makes
an explicit point of not requiring that storage be reused.
****************************************************************
From: Robert Dewar
Sent: Monday, October 25, 2010 4:50 PM
> Also, I think you're misrepresenting Tucker's position a bit:
> He's not proposing any new "strange optimization" -- the
> "optimization" is already clearly allowed in Ada 95, and it would
> probably be REQUIRED if we had some formal way to talk about storage
> leaks.
So if it is allowed in Ada 95 (a surprise to me but OK), we certainly don't want
to change that. So the most we can do is talk about life times, and have IA that
says that stack allocation should be used.
So I don't see anything for Randy to get too upset about, if anyone is assuming
that the thing does not get freed, as in my example, they are writing
non-portable code, and cannot complain if it breaks.
The RM cannot require this behavior, it can only recommend it.
If I understand Randy correctly, then he is saying that he thinks we should make
an incompatible change in Ada 95, and not allow stack allocation. I really think
that we cannot change the current rule in Ada 95! I was confused into thinking
this was a new permission.
If it is an old permission, I don't see what Tuck is excercised about. There is
no way to make this permission a requirement so what's to worry about?
Randy, there is no way to remove this existing permission without creating a
gratuitous incompatibility.
I am hereby convinced to be completely neutral in this discussion of what seems
to me to be a non-issue!
****************************************************************
From: Bob Duff
Sent: Monday, October 25, 2010 4:52 PM
> > Try constructing an example that illustrates the point.
>
> Suppose the access type is converted to type address using UC, and
> passed to a C routine that registers it for some reason or other.
OK, well such code would be wrong, since the "optimization"
is allowed, so such code could result in dangling pointers on some
implementations. But I see the point -- such code might work fine on GNAT, and
if we add the "optimization" it will then fail.
> Note that "easy to imagine" /= "expected to occur frequently in
> practice" !
;-)
Indeed, I think ANY passing of "new T" to an access parameter does not occur
frequently -- much less cases that care about the issue we're discussing.
> > Note that there is no suggestion to change where anything gets
> > finalized, so controlled types are a red herring.
> > The existing finalization rules are compatible with both Tucker's
> > and Randy's view.
>
> OK, I guess I don't know enough, because that does not make sense to
> me, but I am sure that's my fault! :-) Although I note that our expert
> on controlled types is also confused, do perhaps you had better state
> the rule :-)
Well, it's easy to be confused about accessibility rules.
I don't feel like quoting all the rules in their full glory, so I'll just
illustrate with a simple example:
procedure P(X: access T);
P(X => new T);
-- X.all is finalized here, before going to
-- the next statement. GNAT does this right,
-- AFAIK.
Do_Something_Else;
So if Do_Something_Else manages to get its hands on the value that X had (the
pointer) -- perhaps via your 'Address above, then at best it has a pointer to an
already-finalized object, and at worst it has a dangling pointer.
****************************************************************
From: Tucker Taft
Sent: Monday, October 25, 2010 4:44 PM
How about this as a compromise? We disallow allocators for all anonymous access
types when null is specified, but allow implementations that use the stack for
access parameter allocators to continue to do so when a default storage pool is
specified.
****************************************************************
From: Tucker Taft
Sent: Monday, October 25, 2010 4:51 PM
> Note that there is no suggestion to change where anything gets
> finalized, so controlled types are a red herring.
> The existing finalization rules are compatible with both Tucker's and
> Randy's view.
Good point. I presume even if implementations don't allocate these objects on
the stack, they do properly finalize them immediately after the call. I don't
think that is optional. Because of that it does seem a bit obtuse to insist the
storage hang around.
****************************************************************
From: Bob Duff
Sent: Monday, October 25, 2010 5:02 PM
> I understand your point, but you are taking these statements out of context.
>
> We're talking about the behavior of Default_Storage_Pool(null), not
> this (mis)feature in general. What Tucker's suggestion does is takes a
> very simple rule (no use of default storage pools) and adds a bunch of
> verbiage to it to create an exception to the rules. For a case that
> shouldn't happen in the first place, so there can be no value to making a hole in the rules.
> And that hole can cause trouble in the case that someone accidentally
> writes such an allocator (which is *exactly* what this pragma is about
> detecting - accidental use of allocators that don't have defined
> storage pools). So this exception does damage to the intended purpose
> of the pragma. Is this going to happen very often? No, of course not.
> But then again, neither is any other allocator that would be detected
> by this pragma (by definition, a program using this pragma isn't going
> to contain such allocators on purpose).
>
> When you tell Robert that it is very difficult to find a legitimate
> use of an allocator in this context with a longer lifetime, I surely
> agree. The problem really is that people are unlikely to realize all
> of those things that will fail and very well may write such an
> allocator thinking it does what it appears to do (as opposed to what
> it *does* do). And that use will compile correctly but not work. And
> of course, if that happens in some rarely used code, you may simply not detect
> it until the system is fielded. Yuck.
Yes, I understand all that. I even half-agree with it (as I said, I'm trying to be neutral on this point).
To summarize, you object to a loophole in Default_Storage_Pool(null).
I think that's a fair characterization of your position.
What I don't get is why this loophole is so important that you'd kill the whole
Default_Storage_Pool feature.
I've dealt with the alternative in CodePeer. There's a documented coding
convention that every access type has to have a Storage_Pool or Storage_Size=0
clause. But that's a pain in the neck, because without Default_Storage_Pool,
you have to remember to do it. If Default_Storage_Pool can catch 99% of errors
(which are NAMED access types), that's an improvement to Ada.
****************************************************************
From: Bob Duff
Sent: Monday, October 25, 2010 5:09 PM
> How about this as a compromise? We disallow allocators for all
> anonymous access types when null is specified, but allow
> implementations that use the stack for access parameter allocators to
> continue to do so when a default storage pool is specified.
Acceptable to me.
It's unfortunate that compiler writers don't agree on what to do about anon
access alligators, but Default_Storage_Pool isn't making that situation any
worse!
> Sent from my iPhone
Which is, no doubt, why your message came across as one giant-long line. ;-)
I hope you're not texting while driving!
****************************************************************
From: Bob Duff
Sent: Monday, October 25, 2010 5:17 PM
> So if it is allowed in Ada 95 (a surprise to me but OK), we certainly
> don't want to change that. So the most we can do is talk about life
> times, and have IA that says that stack allocation should be used.
That's exactly what my latest version of the AI says.
And Tucker's recent compromise proposal is even weaker
-- it changes "should" to "may", which is acceptable to me, and I hope is
acceptable to Randy.
> If it is an old permission, I don't see what Tuck is excercised about.
Well, I used to be pretty annoyed at the Alsys implementation because it totally
ignored pragma Pack on records in Ada 83, which was surely intended to do
SOMETHING useful, even though it didn't have formal requirements. So I see
Tuck's point of view.
> I am hereby convinced to be completely neutral in this discussion of
> what seems to me to be a non-issue!
So I've won over one warring combatant into the "neutral" camp. ;-)
****************************************************************
From: Randy Brukardt
Sent: Monday, October 25, 2010 5:15 PM
...
> To summarize, you object to a loophole in Default_Storage_Pool(null).
> I think that's a fair characterization of your position.
> What I don't get is why this loophole is so important that you'd kill
> the whole Default_Storage_Pool feature.
The point of Default_Storage_Pool(null) is to detect the use of allocators that
use default storage pools. There is no technical reason that that cannot be done
100% of the time. Making a hole in it so that the case which is most likely to
be unintentionally misused is allowed makes no sense: it just complicates the
whole feature.
I don't find Default_Storage_Pool important enough to saddle it with exceptions
that detract from its purpose. That is, in this particular case, the feature
isn't important enough that a watered down version is worth having. (That's a
value proposition that differs from proposal to proposal, of course.) Your
mileage obviously varies.
****************************************************************
From: Randy Brukardt
Sent: Monday, October 25, 2010 5:22 PM
> How about this as a compromise? We disallow allocators for all
> anonymous access types when null is specified, but allow
> implementations that use the stack for access parameter allocators to
> continue to do so when a default storage pool is specified.
I probably could live with that, because users that really care could use
No_Anonymous_Allocators with that. Unfortunately, that would also throw out the
positive use of anonymous access component allocators (which are only usable
with a specified default pool).
The reason I could live with it is the associated finalization. These allocators
are so screwed up by the current language definition that nothing could make
them work the way everyone (except Tucker) expects.
Note that there is no ACATS test that checks that finalization, so it's dubious
that compilers even get that right. (I know Janus/Ada doesn't, and indeed I have
no idea when or if these ever get finalized.)
I've pretty much concluded that should Janus/Ada simply reject these allocators
always. At least until some customer complains; in that case I'll add a switch
that allows them to work (after lots of work to get the finalization right).
(It's the same strategy that I'm using with coextensions.)
****************************************************************
From: Randy Brukardt
Sent: Monday, October 25, 2010 5:29 PM
...
> If I understand Randy correctly, then he is saying that he thinks we
> should make an incompatible change in Ada 95, and not allow stack
> allocation. I really think that we cannot change the current rule in
> Ada 95! I was confused into thinking this was a new permission.
No, we're only talking about the impact on pragma Default_Storage_Pool.
Tucker wants to make a special-case exception for these things. I don't believe
that they should even be used in code (because of the bizarre, counter-intuitive
semantics), and object to reducing the value of the pragma that way. That's
especially true for Default_Storage_Pool(null), which is supposed to disallow
all allocators of a default storage pool. Making a hole for this case seriously
damages the value of the pragma.
I'm less worried about the case when the pool is specified, because the
requirement to finalize way too soon means you can't do anything useful with the
object anyway, so allocating it so that the storage sticks around forever isn't
particularly valuable.
In any case, I'm not advocating any inconsistent change here, because it would
be the worst kind of change (a silent change at runtime). We have already added
restriction No_Anonymous_Allocators to allow programmers to ban these things
outright. (That should be the default IMHO, but that's another thing we can't
change.)
****************************************************************
From: Robert Dewar
Sent: Monday, October 25, 2010 7:52 PM
> How about this as a compromise? We disallow allocators for all
> anonymous access types when null is specified, but allow
> implementations that use the stack for access parameter allocators to
> continue to do so when a default storage pool is specified. -Tuck
Sounds reasonable
P.S. Tuck, your messages come across as one long line, are you using a Mac
perchance?
****************************************************************
From: Robert Dewar
Sent: Monday, October 25, 2010 7:59 PM
>> To summarize, you object to a loophole in Default_Storage_Pool(null).
>> I think that's a fair characterization of your position.
>> What I don't get is why this loophole is so important that you'd kill
>> the whole Default_Storage_Pool feature.
>
> The point of Default_Storage_Pool(null) is to detect the use of
> allocators that use default storage pools. There is no technical
> reason that that cannot be done 100% of the time. Making a hole in it
> so that the case which is most likely to be unintentionally misused is
> allowed makes no sense: it just complicates the whole feature.
>
> I don't find Default_Storage_Pool important enough to saddle it with
> exceptions that detract from its purpose. That is, in this particular
> case, the feature isn't important enough that a watered down version
> is worth having. (That's a value proposition that differs from
> proposal to proposal, of course.) Your mileage obviously varies.
I find this hugely excessive rhetoric from Randy. It is the worst case I have
seen recently of best being the enemy of good. To decide that you will kill the
whole feature because of the one exception seems completely incomprehensible,
unless it is just a debating position.
****************************************************************
From: Robert Dewar
Sent: Monday, October 25, 2010 8:02 PM
> Note that there is no ACATS test that checks that finalization, so
> it's dubious that compilers even get that right. (I know Janus/Ada
> doesn't, and indeed I have no idea when or if these ever get
> finalized.)
Well obviously I can't speak for Janus/Ada, but certainly GNAT gets this right,
and all AdaMagic front ends do, and I would be amazed if Rational did not have
this right.
****************************************************************
From: Randy Brukardt
Sent: Monday, October 25, 2010 8:30 PM
...
> I find this hugely excessive rhetoric from Randy. It is the worst case
> I have seen recently of best being the enemy of good. To decide that
> you will kill the whole feature because of the one exception seems
> completely incomprehensible, unless it is just a debating position.
Sorry, but I don't find the feature that useful if it has holes allowing various
anonymous access allocators to slip through. *Those* are the cases I'm most
interested in detecting (as there is no way to put a pool on an anonymous
allocator). For named types, searching for all type declarations and making sure
each one has an appropriate pool does the trick; that is neither hard nor
particularly error-prone. (And an ASIS tool can do it automatically if you
want.) But you can't do that for anonymous access types. So you need compiler
support for that case. The exact cases that Tucker wants to allow! That makes
the feature too broken to want in my view.
As I said, your mileage may vary.
****************************************************************
From: Randy Brukardt
Sent: Monday, October 25, 2010 8:44 PM
In the wording:
> The pragma applies to all nonderived access types declared in the
> places defined here, including within an instance of a generic unit.
As written, this breaks the contract model by requiring legality checks in
generic bodies at the point of an instance. We *never* do that. Either this
needs to be written as a post-compilation check, or there needs to be an
assume-the-worst rule, or the check needs to be done on the body when it is
compiled (and not at the point of the instance).
****************************************************************
Questions? Ask the ACAA Technical Agent