!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.) ***************************************************************