Version 1.1 of acs/ac-00255.txt

Unformatted version of acs/ac-00255.txt version 1.1
Other versions for file acs/ac-00255.txt

!standard A.18.4(15.1/3)          13-11-04 AC95-00255/00
!standard A.18.4(97.1/3)
!class confirmation 13-11-04
!status received no action 13-11-04
!status received 13-10-31
!subject
!summary
!appendix

From: Ed Schonberg
Sent: Thursday, October 31, 2013  9:31 AM

Let C be a predefined map container.  The innocuous statement:

   C.Include (Some_Key,  C (X));

is expanded, thanks to the implicit dereferencing to retrieve a container
element, into:

  C.Include (Some_Key,  Indexing (X, C).Element.all);

where Indexing is one of the corresponding aspects of the container, which
returns a reference type with an access discriminant.

The execution of this statement raises Program_Error as an attempt to tamper
with the container, because the indexing call produces an object that needs
finalization, and that sets the tampering bit on the container. The reference is
finalized AFTER the statement, and in the meantime the call to Include does
check the tampering bit and blows up.  This seems to be consistent with the
described semantics of types with implicit dereference, and the expansion given
above is the canonical description of container indexing.

Now it is relatively straightforward to recognize instances of C (X) that in
fact designate container elements, and rewrite its expansion into  Element (C,
X),  which involves no controlled object and does not touch the tampering bit,
making the statement above harmless (it's obviously a convenient notation).  The
question is:  is this rewriting legal ?  The canonical expansion does lead to
that Program_Error, but is it actually mandated by the language?

The proposed rewriting is of course limited to contexts in which it is the
element that is sought, and not a reference to it. Excluding those cases is
simple enough.  Note that the interest of the rewriting is not just to allow the
notation given above, but also to eliminate the machinery that is brought in by
the presence of a controlled object of any lifetime.

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

From: Randy Brukardt
Sent: Thursday, October 31, 2013  2:59 PM

> Let C be a predefined map container.  The innocuous statement:
>
>    C.Include (Some_Key,  C (X));
>
> is expanded, thanks to the implicit dereferencing to retrieve a
> container element, into:
>
>   C.Include (Some_Key,  Indexing (X, C).Element.all);
>
> where Indexing is one of the corresponding aspects of the container,
> which returns a reference type with an access discriminant.

"Indexing" is either going to be "Reference" or "Constant_Reference".

> The execution of this statement raises Program_Error as an attempt to
> tamper with the container, because the indexing call produces an
> object that needs finalization, and that sets the tampering bit on the
> container. The reference is finalized AFTER the statement, and in the
> meantime the call to Include does check the tampering bit and blows
> up.  This seems to be consistent with the described semantics of types
> with implicit dereference, and the expansion  given above is the
> canonical description of container indexing.

I agree with your analysis. This seems to be a case where the Ada semantics
finalizes the expression too late. It's probably not possible (in general) to
determine when "too late" is, however, so the conservative semantics is probably
required.

OTOH, if composite objects are represented by their address (which is the case
in Janus/Ada), finalization (release of the tampering check) really does have to
wait until the object value is copied. So this rewrite is only safe if the
element type is elementary (by-copy).

> Now it is relatively straightforward to recognize instances of C (X)
> that in fact designate container elements, and rewrite its expansion
> into  Element (C, X),  which involves no controlled object and does
> not touch the tampering bit, making the statement above harmless (it's
> obviously a convenient notation).  The question is:  is this rewriting
> legal ?  The canonical expansion does lead to that Program_Error, but
> is it actually mandated by the language?

I think it's mandated by the language (as written). One could consider added an
implementation permission that allows rewriting C.Constant_Reference
(X).Element.all into C.Element (X). But the latter has to involve a temporary in
order to be safe, whereas the former explicitly does not involve a temporary.
Which means that there is a potentially huge cost (depending on the size of the
element).

To make this clearer: the tampering check is intended to ensure that the
designated element does not disappear or get modified while we are holding a
reference to it. I think most compilers pass composite objects by reference, so
this extends into the implementation of Include (if the element type is
composite). That is, the implementation of Include could do something that would
make the value of the parameter (if it is a reference into the same container)
abnormal. The only safe way to deal with this is to make a temporary copy of the
element, and I don't think we want to be doing that automatically (at least in
the case of large elements). (After all, the entire reason that we felt we
needed Constant_Reference was to conveniently avoid the overhead of this copy.)

> The proposed rewriting is of course limited to contexts in which it is
> the element that is sought, and not a reference to it. Excluding those
> cases is simple enough.  Note that the interest of the rewriting is
> not just to allow the notation given above, but also to eliminate the
> machinery that is brought in by the presence of a controlled object of
> any lifetime.

Right. I just worry about the cost for large elements. Obviously, there is no
problem with this for by-copy element types, and there is little problem for
small composite element types. But larger elements really shouldn't be copied,
at least not without the user explicitly requesting it. Of course, we could
mandate that this works without any temporaries, but that would definitely
constrain implementation of routines like Include and Insert. (Especially for
the bounded containers, which can't just swap pointers around. And of course
this would apply to all of the containers; array implementations don't make much
sense for the unbounded maps (for example), but the situation is very different
for vectors and lists.)

Since the user can write C.Element (X) if they really want that, I lean slightly
negative on this idea. But I recognize that there is an improvement in
usability, so I don't want to dismiss it out of hand. What do others think?

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

From: Tucker Taft
Sent: Friday, November  1, 2013  1:21 PM

I believe the RM rules are appropriate here.

Container indexing is a by-reference operation, and as long as that reference is
alive, the container should not be altered.  It seems quite possible that the
container would need to be expanded, for example, as part of the "Include"
procedure, and the expansion of the container could render subsequent use of the
reference erroneous.

I think this is tantamount to changing the discriminant of a record from within
a procedure that has a by-reference IN parameter to some discriminant-dependent
component of the same record.  At least this situation is detected for
containers, whereas for a discriminated record, this would just be erroneous.

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

From: Randy Brukardt
Sent: Friday, November  1, 2013  1:30 PM

I agree in general. In the specific case where the element type is by-copy,
however, there isn't a problem (the reference is not used after the parameter is
evaluated, any container modification would occur much later). We could consider
relaxing the rule in that case only (that would have no additional performance
impact). But probably it isn't worth it, especially as it might cause some weird
(to the user) failures if an existing element type is changed from scalar to
composite.

I don't think it would be a good idea to allow implementations to change from
by-reference to by-copy semantics silently, as that could have a massive
performance impact.

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

From: Ed Schonberg
Sent: Friday, November  1, 2013  1:450 PM

I certainly was not suggesting imposing copy semantics where it is not present.
I was speaking specifically of an context where the compiler has determined that
the desired expression includes an implicit dereference, and its type is the
element type (that is the case in the original example).  But it appears there
is no support for this "optimization" .

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

From: Tucker Taft
Sent: Friday, November  1, 2013  2:23 PM

I agree the current rule is an annoyance, but I think it is important for the
programmer to get used to the idea that indexing is by reference, and understand
the implications.

If we start providing by-copy indexing in some contexts but not all, I think we
are just going to be contributing to long-term confusion as to how user-defined
indexing really works.

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

From: Randy Brukardt
Sent: Friday, November  1, 2013  2:23 PM

> > I don't think it would be a good idea to allow implementations to
> > change from by-reference to by-copy semantics silently, as that
> > could have a massive performance impact.
> >
>
> I certainly was not suggesting imposing copy semantics where it is not
> present.  I was speaking specifically of an context where the compiler
> has determined that the desired expression includes an implicit
> dereference, and its type is the element type (that is the case in the
> original example).  But it appears there is no
> support for this   "optimization" .

But I think you missed the point that for a typical implementation of composite
types, that "dereference" does exactly nothing. That is, the element is passed
by-reference, so it's the address of the element that is passed to the
Insert/Include routine. In that case, you get the same sort of effects that
Tucker notes, even if the element was "dereferenced", because you still are
operating on the address of the original element. The "optimization" only works
if the dereference and/or subsequent parameter passing actually copies the
element.

So, without copy semantics, there is no "optimization" here, you're just turning
a detected error into erroneous execution. And if we were OK with erroneous
execution in the containers, we wouldn't have defined tampering checks in the
first place. So this idea doesn't make any sense in the absence of copy
semantics. (Of course, copy semantics are free for scalar [by-copy] types, so
for those, the "optimization" does make sense.)

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

Questions? Ask the ACAA Technical Agent