Version 1.3 of ai05s/ai05-0019-1.txt

Unformatted version of ai05s/ai05-0019-1.txt version 1.3
Other versions for file ai05s/ai05-0019-1.txt

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

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

From: Pascal Leroy
Date: Monday, November 13, 2006  4:47 AM

> This seems like a hugely incompatible change...

I hate to say I told you so, but I told you so.  I was the lone "against"
vote when this AI was discussed in Palma, because I didn't buy the
argument that "you just move the pragma around".  Messing with the
freezing rules made me extremely uncomfortable, but unfortunately this AI
was only discussed at this one meeting, so I didn't have time to look for
a compelling example that would have killed the proposal.

Now one thing that I didn't realize is that, being a binding
interpretation, it applies to Ada 95.  It would have been bad enough to
ask customers to restructure their code when migrating to Ada 2005, but
forcing them to do that in Ada 95 is intolerable.

I am all in favor of maintaining the invariant that dispatching tables may
be laid out when the type is frozen, but the problem described by the AI
was a relatively obscure wart, and we fixed it by doing open-heart
surgery.  And now that open-heart surgery failed we are going to do a
heart-lung transplant by completely changing the way that tagged types get
frozen.

Changing the freezing rules should only be done with the utmost care.  One
reason is that it's really hard to work out all their consequences.
Another reason is that they are really hard to implement, and that since
(some) compilers make many decisions at the freezing point, any change can
be very destabilizing for implementations.  Finally, these rules are
rather incomprehensible to users, so it's actively harmful to make them
more complicated than they already are.

Incidentally, I implemented this AI fairly recently (for Ada 2005 only),
and was very surprised to see that it led to incompatibilities in our own
code.  Only a very small number of the Amendment changes have had that
effect.  I am not aware of problems in our regression tests, but that must
be because the change has not percolated far enough yet: I am sure that
Claw is one of our tests.  At any rate, I am *not* going to apply this
change to Ada 95, regardless of what the ARG decides: I don't particular
want to p*ss off customers.

Since the problem is relatively marginal, we should  fix it by a small
tweaking of the rules, and drop AI 341.  I believe that something like the
following would work:

"A representation_item for a primitive subprogram of a tagged type shall
appear before the type is frozen."

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

Questions? Ask the ACAA Technical Agent