Version 1.9 of ai05s/ai05-0224-1.txt

Unformatted version of ai05s/ai05-0224-1.txt version 1.9
Other versions for file ai05s/ai05-0224-1.txt

!standard D.7(7)          11-01-28 AI05-0224-1/02
!standard D.7(10.3/2)
!class binding interpretation 10-10-21
!status work item 10-10-21
!status received 10-06-07
!priority Low
!difficulty Medium
!qualifier Error
!subject No_Task_Allocators and classwide returns
!summary
Restrictions No_Task_Allocators and No_Protected_Type_Allocators can be enforced at runtime for allocators of class-wide types.
!question
How does the No_Task_Allocators restriction interact with a function that returns Some_Limited_Type'Class, as in
type Ref is access Some_Limited_Type'Class;
type T is new Some_Limited_Type with record Tsk : Some_Task_Type; end record;
function F return Some_Limited_Type'Class is begin return X : T; end F;
Ptr : Ref := new Some_Limited_Type'Class'(F);
!wording
Add after D.7(7):
In the case of an initialized allocator of an access type whose designated type is class-wide and limited, a check is made that the specific type of the allocated object has no task subcomponents. Program_Error is raised if this check fails.
Add after D.7(10.3/2):
In the case of an initialized allocator of an access type whose designated type is class-wide and limited, a check is made that the specific type of the allocated object has no protected subcomponents. Program_Error is raised if this check fails.
!discussion
Attempting to enforce these restrictions at compile-time for class-wide types would prevent allocators for many types that have no task or protected type components. Enforcing the restrictions with a post-compilation check would be very complex (an error would only occur if a type T has task or protected components AND there is an allocator of a class-wide type that covers T; the allocator and the type T are likely to be in different units). Thus we adopt a runtime check.
!corrigendum D.7(7)
Insert after the paragraph:
the new paragraph:
In the case of an initialized allocator of an access type whose designated type is class-wide and limited, a check is made that the specific type of the allocated object has no task subcomponents. Program_Error is raised if this check fails.
!corrigendum D.7(10.3/2)
Insert after the paragraph:
the new paragraph:
In the case of an initialized allocator of an access type whose designated type is class-wide and limited, a check is made that the specific type of the allocated object has no protected subcomponents. Program_Error is raised if this check fails.
!ACATS Test
Create an ACATS like the example in the question, to verify that the checks are made.
!ASIS
No impact on ASIS.
!appendix

From: Steve Baird
Sent: Monday, June 7, 2010  2:20 PM

How does the No_Task_Allocators restriction interact with a function that
returns Some_Limited_Type'Class, as in

    type Ref is access Some_Limited_Type'Class;

    type T is new Some_Limited_Type with
      record Tsk : Some_Task_Type; end record;

    function F return Some_Limited_Type'Class is
    begin return X : T; end F;

    Ptr : Ref := new Some_Limited_Type'Class'(F);

Does this restriction need a dynamic rule to deal with the cases like this?

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

From: Randy Brukardt
Sent: Monday, June 7, 2010  2:34 PM

I don't think it *has* to have a dynamic rule, since this clearly (!) can be
checked at link-time, and restrictions are technically post-compilation checks
anyway.

The better question in my view is whether we intend implementations to do the
link-time handstands this requires (and if so, why). (You need to do the check
at link-time as the body of F probably is separately compiled from the
allocator, and you need to know about both in order to show a problem.)

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

From: Gary Dismukes
Sent: Monday, June 7, 2010  3:13 PM

> I don't think it *has* to have a dynamic rule, since this clearly (!)
> can be checked at link-time, and restrictions are technically
> post-compilation checks anyway.

But there are cases that might not be checkable at link time, such as when the
function has multiple returns, only some of which have task parts.

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

From: Randy Brukardt
Sent: Monday, June 7, 2010  3:30 PM

> But there are cases that might not be checkable at link time, such as
> when the function has multiple returns, only some of which have task
> parts.

I don't think that people who want to enforce this particular restriction (which
is intended to simplify the runtime system) care whether there is *actually* a
task allocator; I think they only care that there *might be* a task allocator.
After all, you could put if False then around the allocator itself -- but that
would still violate the restriction. So it makes sense to me that any
possibility of returning a task from the function would be flagged if the
function is subsequently used in an allocator.

Specifically, I don't see much difference between:

    if False then
       Ptr := new Some_Type_Containing_a_Task;
    end if;

and

    Ptr := new Root'Class (F);

    function F return Root'Class is
    begin
       if False then
           return Obj:T;
       else
           return Obj2:S;
       end if;
    end F;

(where type T has a task and type S does not).

And the point that I recalled above (that the restriction is intended to
simplify the runtime system) implies that a runtime check is inappropriate.

A more interesting question is what do implementations currently do in this
case? We ought to follow practice if there is in fact any to follow.

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

From: Steve Baird
Sent: Monday, June 7, 2010  3:34 PM

> But there are cases that might not be checkable at link time, such as
> when the function has multiple returns, only some of which have task parts.

Right. Other cases include dispatching calls and calls through
access-to-function values. This is not, in general, checkable at link time.

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

From: Steve Baird
Sent: Monday, June 7, 2010  3:39 PM

I think that if you want to check this restriction completely without runtime
checks, then you would have to be conservative and reject some allocators (e.g.,
the one in my example) on the grounds that the type of the allocated object
*might* have a task part.

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

From: Randy Brukardt
Sent: Monday, June 7, 2010  6:51 PM

Right. The current check is conservative anyway: there is no guarantee that the
allocator of a task will be executed, but the program is rejected simply by its
existence. The only question I would have is "how conservative"? I would think
that banning allocators of all limited class-wide types would be going too far,
so some sort of post-compilation check would be needed. Perhaps simply banning
allocators of a limited class-wide type where any extension has a task component
would be adequate (that would be somewhat easier to implement than trying to
figure out for every function whether it could return a task). In any case, I
think we'll need some additional wording for this rule (whatever we decide).

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

From: Steve Baird
Sent: Thursday, October 21, 2010  4:47 PM

Before trying to come up with wording for this recently-created AI, I need to
know what rule we want to express.

!question

How does the No_Task_Allocators restriction interact with a function that
returns Some_Limited_Type'Class, as in

     type Ref is access Some_Limited_Type'Class;

     type T is new Some_Limited_Type with
       record Tsk : Some_Task_Type; end record;

     function F return Some_Limited_Type'Class is
     begin return X : T; end F;

     Ptr : Ref := new Some_Limited_Type'Class'(F);

Possible solutions include:

    1) If the restriction is given, then conservatively
       reject all access-to-limited-classwide
       allocators on the grounds that they
       might violate the restriction.

    2) A post-compilation check that
       if the restriction is given and an
       allocator exists which allocates an object of type
       Some_Limited_Type'Class, then there exist
       no extensions of Some_Limited_Type which
       have task subcomponents. Or something like that.

    3) A runtime check that raises Program_Error if the
       restriction is in force and the program
       attempts to allocate a task (via some mechanism
       such as the given example which makes it past the
       static checks).


I'd like to get a consensus on what we want here so that I can try to have
wording for it before the Fairfax meeting.

Opinions?

P.S. My preference order is #2, #1, #3.

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

From: Bob Duff
Sent: Thursday, October 21, 2010  5:13 PM

> Before trying to come up with wording for this recently-created AI,

It seems to be so recent that it's in the future.
That is, I don't see it on the ada-auth web site.
And I've never heard of it.

So I'm kind of flying blind here, but...

> P.S. My preference order is #2, #1, #3.

Hmm.  I think #3 is best.

#1 is no good -- why should limited types be punished just because they MIGHT
contain tasks?

#2 is a big pain for implementers, for little gain.

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

From: Steve Baird
Sent: Thursday, October 21, 2010  5:25 PM

>> Before trying to come up with wording for this recently-created AI,
>
> It seems to be so recent that it's in the future.
> That is, I don't see it on the ada-auth web site.
> And I've never heard of it.
>

Yeah, this one just landed in my lap today, but if you've seen the !question section,
then you've seen the whole AI at this point.

> So I'm kind of flying blind here, but...
>
>> P.S. My preference order is #2, #1, #3.
>
> Hmm.  I think #3 is best.

Thanks for taking the time to reply.

I don't feel strongly about it, but my idea was that implementors of a runtime
system which is built for use with this restriction don't want to deal with this
issue at all. They want to know statically that task allocation can't happen.

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

From: Bob Duff
Sent: Thursday, October 21, 2010  5:40 PM

> Thanks for taking the time to reply.

You're welcome.

> I don't feel strongly about it, but my idea was that implementors of a
> runtime system which is built for use with this restriction don't want
> to deal with this issue at all. They want to know statically that task
> allocation can't happen.

Yes, I agree with that.  But replacing every "create some tasks"
with "raise.." seems easy, and accomplishes that goal, while all kinds of
link-time checks seems hard.

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

From: Randy Brukardt
Sent: Thursday, October 21, 2010  5:58 PM

...
> > Before trying to come up with wording for this recently-created AI,
>
> It seems to be so recent that it's in the future.
> That is, I don't see it on the ada-auth web site.
> And I've never heard of it.

It was created out of mail from this list on June 7th. So you *ought* to have
heard of it (but you didn't comment on it at the time).

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

From: Randy Brukardt
Sent: Thursday, October 21, 2010  6:06 PM

...
> > I don't feel strongly about it, but my idea was that implementors of
> > a runtime system which is built for use with this restriction don't
> > want to deal with this issue at all. They want to know statically
> > that task allocation can't happen.
>
> Yes, I agree with that.  But replacing every "create some tasks"
> with "raise.." seems easy, and accomplishes that goal, while all kinds
> of link-time checks seems hard.

Humm. I guess I don't see this, as it seems unlikely to me that the check could
be performed outside of the task supervisor. So quite a bit of the overhead of
creating tasks would be present in the generated code, even if it wasn't used.
That's not the point.

Specifically, I don't see how the function F in Steve's example could be
compiled without the overhead of being able to create tasks, because there is no
way for it to know whether it is called from an allocator (which would raise an
exception) or from an object initialization (which would work - at least in the
absence of other restrictions). And that means that the call site would need at
least some of the overhead of task creation in order to be able to meet the
calling convention of the function (since it couldn't know if a task was
actually created, and no exception could be raised if it was not).

Indeed, that seems to be a problem for *any* version of this restriction other
than the over-the-top #1.

Not sure what to do here...

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

From: Steve Baird
Sent: Friday, November 19, 2010  6:10 PM

Here are some missing pieces for this AI (it currently only has !question and
!appendix sections).

   -- Steve

====


!subject No_Task_Allocators and classwide returns

----

!summary
No_Task_Allocators restriction also implies a runtime check.

----

!question (append to end of existing !question section):

(task allocation is detected at runtime and Program_Error is raised).

----

!wording

Append to the end of D.7(7):

   This rule is enforced via a post-compilation check.

   In addition, a (runtime) check is performed as part of
   the evaluation of any allocator of a type having a
   class-wide limited designated type; a check is made
   that the specific type of the allocated object has no task
   subcomponents. Program_Error is raised if this check fails.
   This check is performed before any task subcomponents of
   of the function result object are activated.

   A runtime check may be needed in the case of an allocated
   object which is initialized by a function call with a
   limited class-wide result type. In this case, the check may
   be performed at the point of the return statement within the
   function with Program_Error being raised at the point of the call
   in much the same way that constraint checks may be performed "early"
   as described in section 6.5's "Implementation Permissions".

----

!discussion

This AI does not address the general question of how restriction pragmas
interact with conditional expressions.

If it is determined that something like

     type Root is tagged limited null record;
     type Ref is access Root'Class;

     type Has_Tasks is new Root with Tsk : Some_Task_Type; end record;


     Ptr : Ref := new Root'Class'(if Flag then Root'(...)
                                 else Has_Tasks'(...));

makes it past the post-compilation check (assuming that a No_Task_Allocators
restriction is given), then the runtime check here will fail if Flag turns out
to be False.

===

Is it ok that all of the wording changes for this AI go into the "static
semantics" section, even though we are discussing dynamic semantics and
implementation permissions? The description of each restriction in this section
is contiguous and it would be awkward to break this one up.

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

From: Randy Brukardt
Sent: Friday, November 19, 2010  9:42 PM

...
>    In addition, a (runtime) check is performed as part of
>    the evaluation of any allocator of a type having a
>    class-wide limited designated type; a check is made
>    that the specific type of the allocated object has no task
>    subcomponents. Program_Error is raised if this check fails.
>    This check is performed before any task subcomponents of
>    of the function result object are activated.
>
>    A runtime check may be needed in the case of an allocated
>    object which is initialized by a function call with a
>    limited class-wide result type. In this case, the check may
>    be performed at the point of the return statement within the
>    function with Program_Error being raised at the point of the call
>    in much the same way that constraint checks may be performed "early"
>    as described in section 6.5's "Implementation Permissions".

This runtime check seems to have way too much description; it's bordering on
verbal diarrhea. No one cares where this check happens, just that it does rather
than performing an action that violates the restriction. We decided during the
call that we don't care if such a program leaks memory -- it's buggy!

Moreover, Max_Tasks merely says "If an implementation chooses to detect a
violation of this restriction, Storage_Error should be raised."

For this one, detection is not optional, but similar wording can be used:

"If an implementation detects a violation of this restriction other than those
detected by the Post-compilation checks, Program_Error should be raised."

AARM Discussion: A runtime check is needed as part of the evaluation of any
allocator of a type having a class-wide limited designated type in case the
actual specific type has a task component. We do not say specifically where the
check is made or the exception raised, but in no case should a task be allocated
before the exception is raised."

That seems like enough, and maybe this can be simplified even further.

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

From: Steve Baird
Sent: Monday, November 22, 2010  4:24 PM

> For this one, detection is not optional, but similar wording can be used:
>
> "If an implementation detects a violation of this restriction other
> than those detected by the Post-compilation checks, Program_Error
> should be raised."

This wording is not appropriate because detection is not optional.
For required detection, I think you need to say "a check is performed"
in there somewhere.

> AARM Discussion: A runtime check is needed as part of the evaluation
> of any allocator of a type having a class-wide limited designated type
> in case the actual specific type has a task component. We do not say
> specifically where the check is made or the exception raised, but in
> no case should a task be allocated before the exception is raised."

I don't think like the idea of using wording appropriate for an optional check
in the RM and then trying to tighten things up with an AARM note.

Incidentally, requiring that a task must not be "allocated" (as you suggested)
is a tighter requirement than requiring that it must not be "activated" (as I
suggested). In the case of a build-in-place function used to initialize an
allocator, it sounds like you are requiring that the check must be performed at
the point of the return statement, not at the call site. Do you agree, and did
you intend this?

I certainly want to allow this behavior, but I'm less sure about requiring it.
Requiring it does, however, eliminate the need for the second of the two
paragraphs that I proposed. We don't need an implementation permission to allow
something that is required. So maybe "allocated" is better than "activated" in
this case.

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

From: Randy Brukardt
Sent: Monday, November 22, 2010  4:57 PM

> > "If an implementation detects a violation of this restriction other
> > than those detected by the Post-compilation checks, Program_Error
> > should be raised."
>
> This wording is not appropriate because detection is not optional.
> For required detection, I think you need to say "a check is performed"
> in there somewhere.

The wording "a check" has a specific semantic meaning in Ada, and I don't
believe that a restriction can ever cause "a check". Moreover, a check has to
occur at a particular place during the execution of the code, and this is
precisely what we do not want to require of restrictions.

So I don't think the wording "a check" is ever appropriate here. Note that there
is no existing restriction which ever uses the wording "a check"; it would be
bizarre for this one to be the first.

> > AARM Discussion: A runtime check is needed as part of the evaluation
> > of any allocator of a type having a class-wide limited designated
> > type in case the actual specific type has a task component. We do
> > not say specifically where the check is made or the exception
> > raised, but in no case should a task be allocated before the exception is raised."
>
> I don't think like the idea of using wording appropriate for an
> optional check in the RM and then trying to tighten things up with an
> AARM note.

I wasn't trying to "tighten things up", just explain what appears to be
necessary and a possible implementation. And I didn't want to use the wording "a
check" here, either, but couldn't find another English word which didn't obscure
the meaning too much. I probably ought to have put "probably" before "is needed"
to make it clear that another implementation is fine.

> Incidentally, requiring that a task must not be "allocated" (as you
> suggested) is a tighter requirement than requiring that it must not be
> "activated" (as I suggested).
> In the case of a build-in-place function used to initialize an
> allocator, it sounds like you are requiring that the check must be
> performed at the point of the return statement, not at the call site.
> Do you agree, and did you intend this?

The restriction is "No_Task_Allocators" not
"No_Tasks_Activated_as_part_of_Allocators". It doesn't make sense to talk about
task activation here. (But I admit I did not notice you were talking about
activation, other than in general you appeared to over-specify the semantics.)

> I certainly want to allow this behavior, but I'm less sure about
> requiring it. Requiring it does, however, eliminate the need for the
> second of the two paragraphs that I proposed. We don't need an
> implementation permission to allow something that is required.
> So maybe "allocated" is better than "activated" in this case.

I don't see that the wording I gave requires any such thing. We decided that we
don't care about memory leakage here, so there is no problem with allocation. If
my wording does require that (or anything else for that matter), it is wrong.

Depending on the task runtime system, it might very well require that no
allocations be done (because there is no code in the RTS to do such
allocations). But other runtimes might not care, if the mechanism to create a
task is the same for allocators and stack-based objects.

I still think you are trying way too hard to over-specify this restriction. I
want the wording to be a vague as possible, since we really don't care when or
where the violation is detected. (I think it would be OK for an allocated task
to start running, so long as the partition never completes normally. Or [more
likely] it would be OK for the exception to be raised as soon as the partition
starts running, if a post-compilation check has determined that the violation
will happen.)

Runtime restrictions are overall things vaguely defined, simply because a
program that violates a restriction is seriously flawed, and we don't care what
they do beyond raising the exception, sometime, somewhere. That goes for *all*
restrictions, not just this one.

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

From: Steve Baird
Sent: Monday, November 22, 2010  5:48 PM

> The wording "a check" has a specific semantic meaning in Ada, and I
> don't believe that a restriction can ever cause "a check".

13.12 says
   A restriction may impose requirements on the runtime behavior of the
   program, as indicated by the specification of runtime behavior
   associated with a violation of the requirement.

To me, this clearly means that restriction theoretically could cause a check.
I'm not claiming that this is terribly important.

> So I don't think the wording "a check" is ever appropriate here. Note
> that there is no existing restriction which ever uses the wording "a
> check"; it would be bizarre for this one to be the first.

What's unique about this restriction is that we seem to have (perhaps
inadvertently) broken new ground by adding the rule that runtime checks to
detect violations of this restriction are required. Is there any other
restriction for which this is the case? I think not. Perhaps we should revisit
this decision. We did not discuss the fact that required runtime enforcement of
a restriction is something new.

If we only require optional enforcement, then I agree with what you have said
about this AI.

Whatever we decide to do probably needs to be duplicated for
No_Proteted_Type_Allocators.

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

From: Alan Burns
Sent: Tuesday, November 23, 2010  2:11 AM

Under Ravenscar there is a requirement (expressed as a restriction I think) that
Program_Error should be raised if there is a suspension/delay inside a protected
object. Without this restriction the situation is a bounded error with 'ignore'
being one of the options. So is this not an example of a restriction requiring a
run-time check?

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

From: Randy Brukardt
Sent: Tuesday, November 23, 2010  12:33 PM

You're thinking of pragma Detect_Blocking (H.5), which is not expressed in the
form of a restriction. (And it doesn't use the term "check", either.)

Steve said:
> What's unique about this restriction is that we seem to have (perhaps
> inadvertently) broken new
> ground by adding the rule that runtime checks to detect violations of
> this restriction are required.
> Is there any other restriction for which this is the case? I think not.
> Perhaps we should revisit this decision. We did not discuss the fact
> that required runtime enforcement
> of a restriction is something new.
>
> If we only require optional enforcement, then I agree with what you
> have said about this AI.

The problem with that is that this restriction is used in Ravenscar.
However, I don't think that is relevant, because Ravenscar also contains
No_Task_Hierarchy, which means that a function can never return a task. Thus the
situation that causes the problem will never arise.

> Whatever we decide to do probably needs to be duplicated for
No_Proteted_Type_Allocators.

That's actually a different case, because there is nothing in Ravenscar
preventing these from being returned from functions. I don't know if that is a
hole in Ravenscar (seems possible) or whether it is intended to allow functions
to initialize library-level protected objects. I suppose one could argue that
No_Local_Protected_Objects disallows a function from returning a protected
object, as the return object would momentarily be considered local; but given
that these are build-in-place objects its surely not as clear-cut.

Alan, is the intent that Ravenscar disallow protected objects returned from
functions? Or did no one ever think about it??

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

From: Steve Baird
Sent: Tuesday, November 23, 2010  12:49 PM

>  I suppose one could
> argue that No_Local_Protected_Objects disallows a function from
> returning a protected object, as the return object would momentarily
> be considered local; ...

One could and I would.

"Momentarily" is a somewhat misleading word in the context of an extended return
statement.

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

From: Randy Brukardt
Sent: Tuesday, November 23, 2010  1:19 PM

True, but we also have to consider a simple return of an aggregate. In that case
(since there is no temporary), a local object is invisible.

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

From: Alan Burns
Sent: Wednesday, November 24, 2010  3:24 AM

Really we did not think about it - we were more concerned about threads
- are there any issues
that would want us to disallow from Ravenscar? I guess silly code could create
an arbitrary number of objects, but that seems more an issue for dynamic
creation of objects anyway and might be part of the restrictions for the
sequential part of any complete language subset.

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

From: Steve Baird
Sent: Friday, January 28, 2011  3:10 PM

Based on yesterday's phone meeting, I now suggest that the !wording section of
this AI consist of only a prefix of what I previously suggested. We should retain
only:

   Add after D.7(7):

   In the case of an initialized allocator of an access type
   whose designated type is class-wide and limited, a check
   is made that the specific type of the allocated object
   has no task subcomponents. Program_Error is raised if
   this check fails.

   Add after D.7(10.3/2):

   In the case of an initialized allocator of an access type
   whose designated type is class-wide and limited, a check
   is made that the specific type of the allocated object
   has no protected subcomponents. Program_Error is raised if
   this check fails.

Please ignore the subsequent wording changes that were previously proposed,
beginning with

   Add after D.7(19.1/2)

   The dynamic checks associated with the No_Task_Allocators
   and No_Protected_Type_Allocators described above ...

and including the associated AARM note.

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


Questions? Ask the ACAA Technical Agent