!standard 3.4(27/2) 13-05-09 AC95-00248/00 !standard 3.2.4(31/3) !class confirmation 13-05-09 !status received no action 13-05-09 !status received 13-02-25 !subject Predicate checking and inherited subprograms !summary !appendix From: Steve Baird Sent: Monday, February 25, 2013 7:09 PM 3.4(27/2) says: For the execution of a call on an inherited subprogram, a call on the corresponding primitive subprogram of the parent or progenitor type is performed; the normal conversion of each actual parameter to the subtype of the corresponding formal parameter (see 6.4.1) performs any necessary type conversion as well. It doesn't say whether a new (wrapper) subprogram is introduced because it isn't supposed to be possible for a user to tell the difference. In particular, given the following context type T is ... ; type D is new T with ... ; subtype S is D with ...; S_Var : S; procedure Op (X : in out T) is ... ; procedure Inherited_Op_Wrapper (X : in out D) is begin Op (T (X)); end; one can think of a call to an inherited op as being more like either Op (T (S_Var)); or Inherited_Op_Wrapper (S_Var); The two models (the non-wrapper implementation model for inherited subprograms and the wrapper model) are semantically equivalent, or at least they are supposed to be. With the introduction of subtype predicate checking, however, the equivalence breaks down (at least slightly) in some cases; this seems undesirable. Either the equivalence should be restored or one one of the two implementation models should be designated as the correct model or it should be explicitly stated that the program behavior associated with either model is permitted. Consider the case where type T is passed by reference. 3.2.4(31/3) says: After normal completion and leaving of a subprogram, for each in out or out parameter that is passed by reference, the predicate of the subtype of the actual is evaluated, and a check is performed that the predicate is True. [The phrase "subtype of the actual" is never defined explicitly, but 6.4.1(17) refers to "the subtype of the variable given as the actual parameter" so presumably that's the subtype in question]. Upon returning from the call to Op in Op (T (S_Var)); the predicate associated with subtype S is checked. Only one predicate check is performed. In the case of Inherited_Op_Wrapper (S_Var); the predicate of D is called upon returning from the call to Op inside of the wrapper subprogram and then the predicate of S is called upon returning from the wrapper. With the wrapper, we get two predicate checks instead of one. True, one of the two checks might be thought of as implying the other, but this would require incorrectly making unjustified assumptions about the implementation of the predicate. One way to get the two models back in synch would be to give them both the double-predicate-check definition that is currently implemented only via the wrapper model. This could be accomplished by changing rules not directly related to inherited subprograms: If a view conversion is passed an actual parameter, the mode is not IN, and the parameter is passed by reference, then upon return from the call the predicate checking associated with the subtype of the conversion is performed followed by that of the subtype of the operand of the type conversion (and repeating if that operand is itself a type conversion). Another way to resolve the portability issue would be to impose a rule that only one check is performed in the above scenario (i.e. the non-wrapper model is right and the wrapper model is wrong). A third solution would be to grant an implementation permission allowing either behavior. Opinions? *************************************************************** From: Randy Brukardt Sent: Monday, February 25, 2013 7:33 PM > 3.4(27/2) says: > > For the execution of a call on an inherited subprogram, a call on the > corresponding primitive subprogram of the parent or progenitor type > is performed; the normal conversion of each actual parameter to the > subtype of the corresponding formal parameter (see 6.4.1) performs > any necessary type conversion as well. > > It doesn't say whether a new (wrapper) subprogram is introduced > because it isn't supposed to be possible for a user to tell the > difference. ... > With the wrapper, we get two predicate checks instead of one. > True, one of the two checks might be thought of as implying the other, > but this would require incorrectly making unjustified assumptions > about the implementation of the predicate. I agree with your analysis, but I don't see the problem. S is a subtype of D. Therefore, the predicate of S includes all of the predicates of D. Thus, the single predicate check of S includes the check for D. Ergo, the only way for the "extra" check of D to be visible is for the predicate of D to depend on some global or have some side-effect. But such a predicate expression violates 11.4.2(27/3) (unless of course the value can never be different, as in a memo function - but then in that case you couldn't detect the difference caused by the redundant evaluations). As such, only non-portable code could possibly depend on the extra check of D. Moreover, there could not be an ACATS test for such as check (the error is not required to be detected, and having a C-test that includes an intentional error is problematical. And we're not in the business of writing tests for pathological cases anyway. > One way to get the two models back in synch would be to give them both > the double-predicate-check definition that is currently implemented > only via the wrapper model. This could be accomplished by changing > rules not directly related to inherited subprograms: > > If a view conversion is passed an actual parameter, the mode is > not IN, and the parameter is passed by reference, then upon return > from the call the predicate checking associated with the subtype > of the conversion is performed followed by that of the subtype of > the operand of the type conversion (and repeating if that operand > is itself a type conversion). > > Another way to resolve the portability issue would be to impose a rule > that only one check is performed in the above scenario (i.e. the > non-wrapper model is right and the wrapper model is wrong). > > A third solution would be to grant an implementation permission > allowing either behavior. > > Opinions? This seems like a tempest in a thimble (it's not even up to teapot standards). A change here looks complex and it surely violates the Duff rule: it's not going to make the slightest difference in any implementation. You would have to have a permission to skip the check on D even in the wrapper case in order for this to have any effect at all (the main cost here being a redundant check), and that seems going too far to me. If there is a problem, it seems to be with 3.2.4(31/3), as you note, it doesn't define "subtype of the actual" and it probably ought to have a clearer definition in the case where the actual is a view conversion and the parameter is by-reference. I suspect that it would be best to mean the subtype of the conversion, and have an additional rule for checking the subtype of the operand of view conversions in this case (this would give your preferred semantics). But as noted above, it doesn't really matter, so I don't see much point to a "fix". *************************************************************** From: Tucker Taft Sent: Monday, February 25, 2013 7:59 PM I'm not sure there is a real issue here. The predicate of "S" includes the predicate of "D" (or equivalently, the predicate of "D" applies to "S" as well). So whenever you check the predicate of S you are checking both. We should certainly allow implementations to avoid redundant checks of the predicate of D, which is presumably already allowed since evaluating a predicate a second time is supposed to produce the same value as evaluating it only once. *************************************************************** From: Randy Brukardt Sent: Monday, February 25, 2013 9:18 PM > I'm not sure there is a real issue here. > > The predicate of "S" includes the predicate of "D" > (or equivalently, the predicate of "D" applies to "S" > as well). Right. The shorter version of what I said. > So whenever you check the predicate of S you are checking both. We > should certainly allow implementations to avoid redundant checks of > the predicate of D, which is presumably already allowed since > evaluating a predicate a second time is supposed to produce the same > value as evaluating it only once. Good point. The Implementation Permission 11.4.2(26/3) already allows omitting redundant evaluations of parts of assertion expressions (even if they have side-effects). Combining that with 11.4.2(27/3) means that no portable program can depend on how many times a part of an assertion expression is evaluated; thus any program that does depend on that is a pathology. And we generally don't mess with the wording of the standard for pathologies. Note that this isn't quite the same as being able to tell. If an assertion causes some logging, one can tell how many times the assertion is evaluated -- but that number is not guaranteed and could change with compiler versions or even optimization settings. *************************************************************** From: Steve Baird Sent: Tuesday, February 26, 2013 12:45 PM > This seems like a tempest in a thimble (it's not even up to teapot > standards). A change here looks complex and it surely violates the Duff > rule: it's not going to make the slightest difference in any implementation. If we assume that folks only write well behaved predicates (or that we don't care what happens if they don't) then I agree. To the extent that a poorly-behaved predicate is a corner case, I concede that we are discussing a corner case. > I'm not sure there is a real issue here. > > The predicate of "S" includes the predicate of "D" > (or equivalently, the predicate of "D" applies to "S" > as well). > > So whenever you check the predicate of S you are checking both. We > should certainly allow implementations to avoid redundant checks of > the predicate of D, which is presumably already allowed since > evaluating a predicate a second time is supposed to produce the same > value as evaluating it only once. Your argument rests on the premise that "evaluating a predicate a second time is supposed to produce the same value as evaluating it only once", as though this is an RM rule of some kind. Let's look more closely at the rules about poorly behaved subtype predicates. Randy cited 11.4.2(27/3) as being relevant; it's not. This Implementation Permission says that implementations "need not allow" certain poorly behaved predicates. In any discussion of the runtime behavior of a program (which therefore must have been successfully compiled and linked), any such permission is clearly irrelevant; if the permitted option had been exercised, we wouldn't have gotten to the point of executing the program. Randy then cited 11.4.2(26/3): If the result of a function call in an assertion is not needed to determine the value of the assertion expression, an implementation is permitted to omit the function call. This pertains primarily to failing assertions: if we are asserting the conjunction of several conditions and one of them evaluates to False, we don't have to evaluate the rest. To claim that it applies in the case of an assertion which passes is a circular argument. We assume without justification that evaluating the predicate twice with the same argument yields the same answer. Then, based on that assumption, we can apply 11.4.2(26/3) to retroactively provide the missing justification. By the same logic, I could make the preposterous assumption that evaluating any predicate twice always yields False the second time and then apply this permission to retroactively justify this assumption and therefore justify replacing any such repeated predicate evaluations with False. *************************************************************** From: Randy Brukardt Sent: Tuesday, February 26, 2013 5:02 PM ... > Randy cited 11.4.2(27/3) as being relevant; it's not. > > This Implementation Permission says that implementations "need not > allow" certain poorly behaved predicates. In any discussion of the > runtime behavior of a program (which therefore must have been > successfully compiled and linked), any such permission is clearly > irrelevant; if the permitted option had been exercised, we wouldn't > have gotten to the point of executing the program. It's relevant because writing such a program is necessarily not portable, even to different versions of the same compiler (as it might start taking advantage of the permission). Our intent (as best as I understood it) was that any program that violated this rule is pathological and bad -- we couldn't find the right words to make that true without throwing out important cases, so we don't have a formal rule to that effect. > Randy then cited 11.4.2(26/3): > If the result of a function call in an assertion is not needed to > determine the value of the assertion expression, an implementation is > permitted to omit the function call. > > This pertains primarily to failing assertions: if we are asserting the > conjunction of several conditions and one of them evaluates to False, > we don't have to evaluate the rest. > > To claim that it applies in the case of an assertion which passes is a > circular argument. We assume without justification that evaluating the > predicate twice with the same argument yields the same answer. Then, > based on that assumption, we can apply 11.4.2(26/3) to retroactively > provide the missing justification. You might be right that "we assume without justification", but that was the entire point of 11.4.2(27/3) - to insure that no evaluation of an assertion expression ever gets a different answer when reevaluated. Perhaps we need some different wording to capture this rule clearly, but it should NEVER be the case that the compiler has to evaluate the same assertion subexpression twice as part of evaluating a single set of assertion checks. > By the same logic, I could make the preposterous assumption that > evaluating any predicate twice always yields False the second time and > then apply this permission to retroactively justify this assumption > and therefore justify replacing any such repeated predicate > evaluations with False. I don't see this logic at all. The initial "assumption" is supported by 11.4.2(27/3) (and often directly with knowledge about the purity of the functions involved); I can't imagine any rules of Ada that would let your assume a second evaluation would return false (short of body inspection). To make this issue even worth the time I've already spent on it, you need to come up with an example that does not violate 11.4.2(27/3) where the difference between evaluating the predicate once or twice actually makes some sort of relevant difference. Otherwise, it seems like a completely pointless argument (how many assertion checks fit on the head of a pin?). *************************************************************** From: Tucker Taft Sent: Tuesday, February 26, 2013 5:10 PM I agree with both of you. The wording as given is inadequate, but the intent is as Randy stated: you may assume that if you evaluate an assertion expression once and it is True, you don't need to evaluate it again if all you are doing in the mean time is evaluating assertion expressions. *************************************************************** From: Steve Baird Sent: Tuesday, February 26, 2013 5:21 PM I'm happy to leave it at that. Let's either take no further action or, if anyone thinks it is worth the bother, add a brief AARM note based on Tuck's words above. [Editor's note: The AARM note is mentioned in AI12-0005-1.] ***************************************************************