!standard 13.14(15.1/2) 06-11-10 AI05-0019-1/01 !class binding interpretation 06-11-10 !status work item 06-11-10 !status received 06-11-09 !priority High !difficulty Hard !qualifier Error !subject Primitive subprograms are frozen with a tagged type !summary The types of tagged parameters are not frozen when the subprogram is frozen. !question RM-13.14(15.1/2) says: At the place where a specific tagged type is frozen, the primitive subprograms of the type are frozen. This seems like a hugely incompatible change, and in fact it breaks a lot of existing code. Here's an example. We have two tagged private types. Freezing T2 causes Primitive to be frozen (by AI95-00341). That causes its parameter types to be frozen, which freezes T1'Class, which freezes T1, which is illegal, because T1 is not fully defined. package P is type T1 is tagged private; type T2 is tagged private; procedure Primitive (X : T1'Class; Y : T2); private type T2 is tagged null record; Object : T2; -- Freeze T2 here. type T1 is tagged null record; end P; Is this intended? (No.) !recommendation (See Summary.) !wording TDB. !discussion Here's another example of the problem with the original rule: package P is type T1 is tagged private; type T2 is tagged private; type Parent is tagged null record; procedure Primitive (X : T1'Class; Y : T2; Z : Parent); private type T1 is new Parent tagged null record; -- Freeze Parent here. type T2 is with null record; end P; Some rule is needed to avoid problems with the convention of inherited routines (see 3.9.2(10/2)). Without some rule, the convention of a primitive routine can be changed after it is inherited. Similarly, it was always the intent of Ada that the tag could be built when the type is frozen. The primitive subprograms need to be frozen for that to be possible. OTOH, the level of incompatibility incurred was unanticipated by AI95-00341. Thus, some adjustment to the rule is needed. We already allow incomplete tagged views to be parameters to subprograms. Such a view has about the same amount of information that an unfrozen tagged type would, and compilers already need to know how to handle them. Thus, exempting them from freezing should not have any effect on compilers. The tagged type should be frozen before any dispatching call needs to be generated (non-dispatching calls are not a problem, as tagged types are by-reference; but dispatching calls need to know the location of the tag, presumably determined when the type is frozen). This rule change will not eliminate all incompatibilities with this rule, just the worst cases. Non-tagged parameters and all function return types will continue to be frozen, possibly requiring some reorganization of the code. !ACATS test Make a C-Test that is similar to the example, and possibly B-Tests for any remaning incompatibilities. !appendix From: Robert A. Duff Date: Friday, November 10, 2006 11:13 AM RM-13.14(15.1/2) says: 15.1/2 {AI95-00341-01} At the place where a specific tagged type is frozen, the primitive subprograms of the type are frozen. This seems like a hugely incompatible change, and in fact it breaks a lot of AdaCore's customer's code. Am I misinterpreting this rule? If not, I presume this rule applies to Ada 95, since the AI is classified as a binding interpretation. Right? Here's an example. We have two tagged private types. Freezing T2 causes Primitive to be frozen (by AI95-00341). That causes its parameter types to be frozen, which freezes T1'Class, which freezes T1, which is illegal, because T1 is not fully defined. package P is type T1 is tagged private; type T2 is tagged private; procedure Primitive (X : T1'Class; Y : T2); private type T2 is tagged null record; Object : T2; -- Freeze T2 here. type T1 is tagged null record; end P; Here's another example: package P is type T1 is tagged private; type T2 is tagged private; type Parent is tagged null record; procedure Primitive (X : T1'Class; Y : T2; Z : Parent); private type T1 is new Parent tagged null record; -- Freeze Parent here. type T2 is with null record; end P; It is clear from the AI that this was not intended. For example, it says, "It is not legal to declare any additional operations after the freezing of the type anyway, so this change will not have any effect on what can be declared." And the "!ACATS test" section worries about how to construct a portable address clause, which is a pretty obscure issue. **************************************************************** From: Robert Dewar Date: Friday, November 10, 2006 11:31 AM > This seems like a hugely incompatible change, and in fact it breaks a lot of > AdaCore's customer's code. Am I misinterpreting this rule? Note: it does seem to be needed if you want to statically build the dispatch table at the point of freezing. We just installed new code to do just that, which depends on the above rule, so that's how we ran into a bunch of incompatibilities (CLAW is by the way one of the programs that bumps into this :-)) > > If not, I presume this rule applies to Ada 95, since the AI is classified as a > binding interpretation. Right? I definitely think that if we decide to keep this rule, we should hold our noses and say it applies to Ada 95, and that we intended this all along. **************************************************************** From: Robert A. Duff Date: Friday, November 10, 2006 11:37 AM >...(CLAW is by the way one of the > programs that bumps into this :-)) And SofCheck Inspector is another one. ;-) **************************************************************** From: Tucker Taft Date: Friday, November 10, 2006 11:45 AM I think it is intended to apply to Ada 95. There is nothing about it that is specific to Ada 2005 features. It seems like a reasonable rule as well. Certainly the example you give below can easily be adjusted to accommodate the rule. I can imagine more complicated situations might exist, of course. The freezing rules have always been a bit difficult in the area of tagged type primitives, such as the rule requiring no additional primitives after a type derivation. This makes them a bit more so, but I think there are good reasons behind the rules. So I think the customers will have to adjust to the new rule. I don't see the rule being eliminated or reinterpreted. **************************************************************** From: Robert Dewar Date: Friday, November 10, 2006 11:35 AM > I definitely think that if we decide to keep this rule, we should hold > our noses and say it applies to Ada 95, and that we intended this all > along. Note that I am equally happy with dumping the rule (it would cause us to go back and rethink how to build dispatch tables, but that's not so terrible). **************************************************************** From: Randy Brukardt Date: Friday, November 10, 2006 12:08 PM > This seems like a hugely incompatible change, and in fact it breaks a lot of > AdaCore's customer's code. Am I misinterpreting this rule? I doubt it. It was always intended that it be possible to build tags at the freezing point, and we need to ban rep. clauses (specifically pragma Convention) from happening after that point. > If not, I presume this rule applies to Ada 95, since the AI is classified as a > binding interpretation. Right? Correct. > Here's an example. We have two tagged private types. Freezing T2 causes > Primitive to be frozen (by AI95-00341). That causes its parameter types to be > frozen, which freezes T1'Class, which freezes T1, which is illegal, because T1 > is not fully defined. I don't think we thought about the freezing of parameters that is a side-effect of this rule. One could try to patch up these examples by saying that class-wide parameters aren't frozen when the subprogram is (they're pass by reference, so we don't need to know anything about them to generate calls -- a fact that limited views depend heavily on; and the occurrence of the body will freeze them before any dispatching call could be made). A similar exception could be made for access-to-class-wide. But that seems like a fairly weird exception to the rules, and undoubtedly there are examples that don't depend on class-wide types. I'm not sure if it is worth it (not having seen how CLAW has problems with this new rule... ;-) **************************************************************** From: Tucker Taft Date: Friday, November 10, 2006 2:11 PM Given that we allow incomplete tagged types as parameters, it seems like we could easily (and probably should) allow a special case for tagged type parameters in the freezing rules. That is, when a subprogram is frozen, the return subtype and the non-tagged parameter subtypes are frozen, but not the tagged parameter subtypes. This would presumably relieve some of the incompatibility pain. **************************************************************** From: Randy Brukardt Date: Friday, November 10, 2006 2:36 PM Yes, that would be fine for the primitive subprogram freezing (because the controlling operands would necessarily be frozen). But I think it might cause a problem with dispatching calls in other cases (not completely sure though, because I can't off hand think of a way to get access to an unfrozen object. We can dereference incomplete types, but other rules make dispatching calls from such objects impossible.) After all, the call freezes the subprogram, but if freezing the subprogram doesn't freeze the parameters, things could get sticky. We should certainly check this idea out more carefully, because freezing unrelated class-wide types that happen to be used as parameters in primitive operations is unpleasant. (I don't relish trying to restructure Claw -- it was hard enough to structure as it is.) **************************************************************** From: Robert A. Duff Date: Friday, November 10, 2006 2:27 PM > I think it is intended to apply to Ada 95. OK, good, at least we agree on that point. > The freezing rules have always been a bit difficult in the > area of tagged type primitives, such as the rule requiring > no additional primitives after a type derivation. This makes them a bit > more so, but I think there are good reasons behind the > rules. There may be good reasons behind the freezing rules in general. I think everyone interested in this topic should go back and read the AI, including the appendix. It will become clear that for THIS particular rule, the reasons are rather weak. It comes across as an offhand conversation between Randy and Tuck -- Should we do this? Sure, what the heck, why not? The AI talks about obscure stuff like address clauses and pragmas import on primitive ops of a tagged type. It's clear that the wider ramifications -- incompatibility -- were not considered at all. **************************************************************** From: Robert A. Duff Date: Friday, November 10, 2006 2:40 PM > I doubt it. It was always intended that it be possible to build tags at the > freezing point, and we need to ban rep. clauses (specifically pragma > Convention) from happening after that point. Yes, it was intended, and this rule should have been there from the start. But it wasn't, so we have a compatibility problem, and AdaCore has proof in our bug-report database that this is a problem in practise. I have no problem with banning Convention and Address clauses after that point, which is all the AI talks about. I think this is a simple case where ARG didn't realize the compatibility issue. Note that the AI has some handwringing about how it will be difficult to even construct an ACATS test that deliberately violates the rule! I would tolerate the incompatibility if there were some practical benefit, but here, we're just trying to obey a language design principle for no concrete reason. I think we should look for a narrow rule that worries specifically about Convention/Address. Randy and Tucker have made some suggestions, but I don't fully understand the ramifications. If we can't build dispatching tables at the freezing point of the type, then too bad, we'll have to build them at the latest of the freezing point of the type and freezing points of the primitives. A little sad, but hardly worth this incompatibility. **************************************************************** From: Robert Dewar Date: Friday, November 10, 2006 3:08 PM > If we can't build dispatching tables at the freezing point of the type, then > too bad, we'll have to build them at the latest of the freezing point of the > type and freezing points of the primitives. A little sad, but hardly worth > this incompatibility. More than a little sad, means throwing away a months work and missing the release on the static dispatch tables, oh well :-( **************************************************************** From: Tucker Taft Date: Friday, November 10, 2006 4:43 PM > Yes, that would be fine for the primitive subprogram freezing (because the > controlling operands would necessarily be frozen). But I think it might > cause a problem with dispatching calls in other cases (not completely sure > though, because I can't off hand think of a way to get access to an unfrozen > object. We can dereference incomplete types, but other rules make > dispatching calls from such objects impossible.) After all, the call freezes > the subprogram, but if freezing the subprogram doesn't freeze the > parameters, things could get sticky. I don't see why, if we are only talking tagged types. Presumably the actual parameter must be an object of the formal parameter type or a derivative of it, so clearly by then the formal parameter will have been frozen. I don't think dispatching makes it any harder at the point of the call. > > We should certainly check this idea out more carefully, because freezing > unrelated class-wide types that happen to be used as parameters in primitive > operations is unpleasant. (I don't relish trying to restructure Claw -- it > was hard enough to structure as it is.) Bob and Robert haven't commented on the above suggestion. I'm curious whether it would resolve Bob's upward compatibility concerns. **************************************************************** From: Randy Brukardt Date: Friday, November 10, 2006 5:24 PM > Bob and Robert haven't commented on the above suggestion. > I'm curious whether it would resolve Bob's upward compatibility > concerns. Well, Bob in fact did, an hour ago: "Randy and Tucker have made some suggestions, but I don't fully understand the ramifications." Moreover, I think his mail indicates that he'd just as soon we repeal the rule altogether and give up on the static tag model. (He believes the incompatibility is too great to be justified for this corner case.) Robert's messages seemed more sympathetic to keeping the rule (possibly with tweaks). I surely don't want to claim to understand all of the ramifications either, but here are some: (1) Without the rule, we allow "late" use of pragma Convention. That would make the rules about inherited conventions into a total mess. See 6.3.1(13.2/1) and 3.9.2(10/2). That's because the convention of a primitive could change after it was inherited (assuming that freezing of the parent type didn't freeze primitives). Yes, this isn't very likely in practice, but it's an ugly case that needs to be addressed. (2) The change proposed by Tucker surely eliminates any incompatibility in the examples given by Bob (in both cases, the problem parameter was a class-wide one, which certainly is tagged). (3) Examples with incompatibilities would surely continue to exist (you can write similar cases class-wide function results, and with untagged types). I think these are much less likely to occur in practice, and the untagged ones would be easier to work around in practice (elementary types cannot depend on the tagged ones, so they can be moved easily; records could be converted to tagged types if they couldn't be moved). But almost certainly, we've need to find out if real-world code still has problems, and that could only be done with a sample implementation. (4) The proposed change is safe in all cases except (possibly) dispatching calls. That's because we already allow incomplete tagged views to be parameters; they have even less information than an unfrozen tagged type. If a compiler can handle the first, it surely can handle the latter. So the change would be reasonably safe; it could be completely safe by saying that it only applies to non-controlling tagged parameters. (That would have no effect on the compatibility issue, as the type that the operation is primitive for is already being frozen, and no other type also could be controlling as that would violate the only one controlling type rule. But of course the rule would be more complex.) Because of (1), just reverting to the Ada 95 rule really doesn't work. (It didn't work in Ada 95, either.) I suspect Bob would probably be happier if we just fixed (1) by inventing a new kind of freezing for subprograms that freezes the subprogram (thus preventing later rep. clauses) without freezing the parameters. That would certainly solve the original problem, but I wonder about the ramifications of that -- would that really allow static tag creation, or are the forms of the parameters needed for that? (I don't think Janus/Ada needs to know the forms of the parameters to create a tag, but I don't want to extrapolate.) I would like to find out if Tucker's change actually reduced the incompatibility to manageable levels, or if it really doesn't help. It's hard to decide what to do without that information. But I think someone would have to have a compiler implementing Tucker's proposal in order to see what the effect was to get it: this is just too complicated to check by hand. Perhaps that would be easy enough to implement in the development version of GNAT that shows these incompatibilities -- if so, the information would be greatly appreciated. **************************************************************** From: Edmond Schonberg Date: Friday, November 10, 2006 5:49 PM The concerns that Bob first voiced come from the implementation of the AI-341 rule in GNAT (part of a move to build static dispatch table). We found two dozen tests in our regression suite that were now rejected. We are checking that those rejections are correct, i.e. that our implementation is not crying wolf unnecessarily. Most of the cases I have seen involve a class-wide parameter, so it is very likely that Tucker's relaxed rule will fix those. However, it is really a pity to give up the possibility of building static dispatch tables, and for that the subprograms have to be frozen. We will report in a few days on whether the cases that are properly rejected are hard to fix, or require just moving some declaration further down, in which case we can tell users to adapt. **************************************************************** From: Robert A. Duff Date: Saturday, November 11, 2006 7:58 AM > Well, Bob in fact did, an hour ago: "Randy and Tucker have made some > suggestions, but I > don't fully understand the ramifications." Moreover, I think his mail > indicates that he'd just as soon we repeal the rule altogether and give up > on the static tag model. No! I definitely think the dispatch tables should be statically allocated and statically initialized. If the rule in question is necessary to make that possible, then I think we should keep it. (Or Tuck's suggestion, presuming that's less incompatible.) Why is it necessary? I understand the Convention and Address issues mentioned in the AI, but what else? I'm curious as to what other compilers do with the examples I sent, and whether they do in fact build static dispatch tables. **************************************************************** From: Robert Dewar Date: Saturday, November 11, 2006 8:07 AM We will try implementing the relaxation rule suggested and see how many of the "incomaptibilities" this removes, and report back. **************************************************************** From: Tucker Taft Date: Saturday, November 11, 2006 12:55 PM We have always built static dispatch tables. But we don't actually use the "official" freezing point to decide when to build them. We build them as soon as we know enough (at compile-time) to do so. If that happens after the "official" freezing point, it doesn't really matter. We assign the tables a "linkname" very early in the process, so we can generate references to the tables before we generate the tables themselves. **************************************************************** From: Randy Brukardt Date: Monday, November 13, 2006 12:18 AM Yes, that sounds about like what happens in Janus/Ada. We assign a symbol at the point of the type declaration for the tag, and actually build the tag at the end of the package or declarative part. (I know we tried doing it at the freezing point, and it didn't work for some reason, so we moved it.) ****************************************************************