AI22-0051-1

!standard 7.3.3(7/5)                                    23-05-19  AI22-0051-1/04

!standard 10.2.1(5)

!standard 10.2.1(7/5)

!class Binding Interpretation 22-10-26

!status No Action  12-0-0   23-12-14

!status work item 23-08-18

!status ARG Approved  6-0-0  23-06-11

!status work item 22-10-26

!status received 22-10-26

!priority Medium

!difficulty Medium

!qualifier Omission

!subject Preelaborable_Initialization and contract aspects

!summary

We ignore the four assertion-ish checks that might be performed as part of default initialization (Default_Initial_Condition, Type_Invariant, Static_Prediate, and Dynamic_Predicate) when deciding whether something is preelaborable, and then permit implementations to either omit the checks, or defer them.

!issue

A Default_Initial_Condition (D_I_C) expression is evaluated when a default-initialized object is created. The aspect Preelaborable_Initialization (P_I) does not take into account any such evaluation when it determines whether a component has P_I. That means that such an evaluation could execute operations not otherwise allowed during the elaboration of a preelaborated package (in particular, calling a function other than the handful allowed for preelaboration). This seems to circumvent the purpose of aspect P_I.

!recommendation

(See Summary.)

!wording

Add after 10.2.1(9.1 /5):

For the purposes of the above rules, checks associated with the aspects Default_Initial_Condition, Type_Invariant, Static_Predicate, or Dynamic_Predicate are ignored.

Add after 10.2.1(11.8 /5):

Implementation Permissions

Any checks associated with the aspects Default_Initial_Condition, Type_Invariant, Static_Predicate, or Dynamic_Predicate that would normally accompany the elaboration of a construct that is part of a preelaborated unit, may be omitted completely, or deferred until the beginning of the elaboration of the first library unit that is not preelaborated.

!discussion

This problem occurs in a number of cases, not just Default_Initial_Condition.

(1) The Default_Initial_Condition is evaluated for any default-initialized object of the type associated with the aspect (see 7.3.3(7/5)).

(2) The Type_Invariant is evaluated for any default-initialized object of the type associated with the aspect (see 7.3.2(10/4)).

(3) A Dynamic_Predicate can be evaluated for any default-initialized object of a type with a Default_Value (since the value is converted to the appropriate subtype, and that subtype can have a Dynamic_Predicate, see 3.3.1(11.1/5)). [Note: A Static_Predicate cannot evaluate anything that is not preelaborable.]

(4) A Dynamic_Predicate can be evaluated for any default-initialized object of a type with a Default_Component_Value (since the component value is converted to the appropriate subtype, see 3.3.1(13/5)). Case (3) can also happen for an array object (that also is mentioned in 3.3.1(13/5)).

(5) A Dynamic_Predicate can be evaluated for any component of a default-initialized object if the component has a default expression; again, the component is converted to the appropriate subtype, see 3.3.1(13/5).

None of these cases are covered by the current wording in 10.2.1. It might appear that case (5) is covered by 10.2.1(11.3/2), but it is not; that paragraph talks about the evaluation of the default expression; it does not say anything about the subtype conversion that happens afterward.

—-

Various solutions were considered:

We have adopted a combination of the final two bullets, though we do not require the check to be deferred. It can be omitted, deferred, or performed at the usual time, if that does not interfere with other requirements of preelaboration.

----

Note that the simple solution of not allowing a type that runs afoul of any of these cases to have Preelaborable_Initialization would make it impossible to have a preelaborable package that uses Default_Initial_Condition. A D_I_C is only allowed on a private type, and as such, any useful D_I_C necessarily has to make at least one function call to query the state of the object. So we have adopted the proposal to ignore these assertion-ish checks to determine whether a type has preelaborable initialization, and allow implementations to omit or defer the associated checks.

!example

The Ada.Containers packages illustrate this issue nicely. Following is the specification of the Ada.Containers.Vectors package, showing just the parts relevant to this discussion:

generic
   ...
package Ada.Containers.Vectors
   with Preelaborate, ... is
   type Vector is tagged private
      with ...
           Default_Initial_Condition =>
              Length (Vector) = 0 and then ...,
           Preelaborable_Initialization;

The elaboration of a default-initialized Vector object will finish with the evaluation of the Default_Initial_Condition expression. That will call the function Length. However, that means that such an elaborable construct is not preelaborable by the rules given in 10.2.1(5, 7). Yet we are asserting that this construct is preelaborable by the use of the P_I aspect.

With the new rules, the Vector type can have both preelaborable initialization and a D_I_C aspect. The implementation need not enforce the D_I_C check when it would interfere with preelaboration.

!ACATS test

There should be an ACATS C-Test to verify that D_I_C and other similar aspects are not considered when deciding whether a type can have a Preelaborable_Initialization aspect. There also should be an ACATS C-Test that in a case where an object of such a type is declared other than in a preelaborated package, D_I_C, Type_Invariant, etc. are in fact checked. The ACATS generally does not test Implementation Permissions, and it would be difficult to test the effect of the permission in a preelaborated package anyway, so no test for that case is recommended..

!appendix

This AI was promoted from AI12-0420-1, which was put into the Hold state as it was reported too late to find a solution in time to make the deadline for Ada 2022. This AI is associated with Github Issue #30 (https://github.com/Ada-Rapporteur-Group/User-Community-Input/issues/30).


 

From: Niklas Holsti

Sent: Friday, August 18, 2023  3:05 AM

> Following is the list of approved AIs that are intended (in the

> absence of

> problems) to be submitted to WG 9 for approval at their October

> meeting. The deadline of August 25th, 17:00 hours CDT (-5 UTC) is

> intended to give me enough time to apply any changes and get them to

> WG 9 by their 6 week deadline.

I am puzzled by AI22-0051-1/04. The motivation seems too weak for doing something so un-Ada-like as to silently omit the execution of checks that the programmer has explicitly coded, which is the solution suggested in the AI.

The issue is that an object of a type for which the program specifies both Preelaborable_Initialization and Default_Initial_Condition (or one of the other listed assertion aspects) cannot now be created in a preelaborable context, because the checks required by those aspects require function calls during object initialization, which is not allowed in preelaboration.

The AI suggests to silently omit those checks during preelaboration, or allow them to be done later, at the start of (normal) elaboration, as the implementation chooses.

I propose, instead, to define a new policy_identifier for pragma Asssertion_Policy, called, for example, Ignore_For_Preelaboration. To allow the creation of objects with a Default_Initial_Condition aspect in a preelaborated package, one would have to add, at the start of the package,

        pragma Assertion_Policy (

           Default_Initial_Condition => Ignore_For_Preelaboration);

or specify that as a configuration pragma. Probably one could usually say just

        pragma Assertion_Policy (Ignore_For_Preelaboration);

to cover all kinds of assertions.

Requiring such a pragma would make the omission of the checks explicit and visible.

I suppose we could still allow an implementation to delay the execution of the checks until after preelaboration. If we don't want to let that be automatic, it could be requested by something like

        pragma Assertion_Policy (Check_After_Preelaboration);


 

From: Tullio Vardanega

Sent: Friday, August 18, 2023  3:11 AM

Thanks Niklas.

Your suggestions to make much sense to me.


 

From: Tucker Taft

Sent: Friday, August 18, 2023  7:54 AM

I would agree that we should probably re-open this one, but I think simpler would be to simply always require the check (in the absence of Assertion_Policy (Ignore)), but allow it to be deferred.  From an implementation point of view, I suspect detecting the situation, checking some new assertion policy, allowing the deferral under certain circumstances, etc., is going to be sufficiently complex that the better solution is to require the check, and let the implementation decide whether deferring it is necessary.


 

From: Bob Duff

Sent: Friday, August 18, 2023  8:06 AM

That makes good sense.  Simpler also from user's point of view.


 

From: Randy Brukardt

Sent: Sunday, August 20, 2023  2:28 AM

Tucker's suggestion makes more sense than the Niklas's proposal, but I am concerned that the cost would be quite high. In particular:

(1) Requiring a new policy for these cases would be incompatible with previous versions of Ada. In particular, containers have the Default_Initial_Condition aspect, and requiring a new policy to use them in some (existing) contexts would clearly be incompatible. I would guess that the use of containers is relatively rare in Preelaborated units, but the idea of making contracts incompatible with preelaboration in any context is horrible.

(2) As Bob touched on, users would find this requirement off-putting. The use of containers and most other ADTs would be illegal in certain contexts. A friendly compiler could suggest to change the Assertion_Policy, but I'd guess most users would find that to be another Ada hoop rather than a helpful thing.

(3) Implementation of deferring checks would definitely be painful in Janus/Ada. The various contracts associated with default initialization naturally are included in the default initialization subprogram (thunk) generated for each record type. This automatically ensures that the contracts are enforced for subcomponents as needed (as the subcomponents call the appropriate thunk). However, that would not work if the contract checks would have to be deferred in some obscure case. In that case, the contract checks would have to be isolated into their own thunks, which would increase the size of the code (for the extra call overhead and the extra subprogram wrappers needed).

(4) For Default_Initial_Condition (DIC) specifically, the rules on what can be preelaborated mean that the function has to essentially be a constant (in the absence of discriminants). The default initialization cannot be a function or anything else dynamic, and thus the DIC can only test for appropriate constant components. A check for such a function should be able to be optimized away by most if not all compiler technology. (If it can't be optimized away, the definition has a substantial bug and it should be flagged when the type is declared -- why wait for a use?). So whether or not the check is made at a usage site is pretty much irrelevant (failure is not an option ;-). This might not be true for all of the contracts that can be affected here, but any cases that aren't are in the noise.

(5) More generally, all of the contracts in this case are those that are *not* affected by anything the client does. These contracts restrict what the source package can do; they don't change because of the client's actions. Thus, they should *never* fail in client code; if they do, that is a bug in the source package, not in the client's code. So a failure doesn't provide anything useful to the client (unless of course they are also the author for the source package). One could make an argument that they shouldn't be checked in any client code; it's surely not harmful to omit the checks in this case.

(6) I don't believe that Preelaboration does anything useful. It mainly allows the compiler to make certain optimizations that the compiler could (and should) make in any appropriate case (with or without pragmas). The only effect it has in Janus/Ada is to make some code illegal because of its rules. As such, I don't want to spend much effort on it at any level -- certainly not for implementation nor for the standard.

My conclusion from the above is that we have it right currently. The only alternative I see is moving Preelaboration to Annex J and removing it completely from the core of the RM (having it be a pragma that has no effect). That's probably too radical for most.


 

From: Tucker Taft

Sent: Sunday, August 20, 2023  7:28 AM

>Tucker's suggestion makes more sense than the Niklas's proposal, but I am concerned

>that the cost would be quite high. In particular:

>

>...

>

>(6) I don't believe that Preelaboration does anything useful. It mainly allows the

>compiler to make certain optimizations that the compiler could (and should) make in

>any appropriate case (with or without pragmas). The only effect it has in Janus/Ada

>is to make some code illegal because of its rules. As such, I don't want to spend

>much effort on it at any level -- certainly not for implementation nor for the standard.

If this is true, what happens if you just ignore the presence of D_I_C (or similar aspects) when deciding what to make it illegal, but otherwise generate exactly the same code?  The only danger would be if the D_I_C code accessed something that isn't yet elaborated.  But that seems very unlikely, since the type used in the preelaborated package must itself be declared in a pure or preelaborated package, so anything the D_I_C code does can only be referring to data that has itself been preelaborated.  It seems that actually deferring the checks is never going to be necessary to avoid an elaboration error.  Yes, there might be a function call in officially preelaborated code, but it is a call on something that has been already been elaborated.  RM C.4 establishes additional implementation requirements associated with preelaborated units, but we could clearly adjust that to take into account the possible run-time effects of a D_I_C aspect.  In fact, perhaps we can eliminate the permission to defer the checks based on this reasoning, and simply loosen the rules in C.4 to properly account for things like D_I_C.

>My conclusion from the above is that we have it right currently. The only

>alternative I see is moving Preelaboration to Annex J and removing it completely from

>the core of the RM (having it be a pragma that has no effect). That's probably too

>radical for most.

I don't see the need for this.  Based on the above reasoning, it would seem we do not need to defer the checks, but rather need to loosen C.4 a bit in their presence.


 

From: Randy Brukardt

Sent: Monday, August 21, 2023  7:49 PM

>>Tucker's suggestion makes more sense than the Niklas's proposal, but I

>>am concerned that the cost would be quite high. In particular:

           

>>...

           

>>(6) I don't believe that Preelaboration does anything useful. It mainly allows

>> the compiler to make certain optimizations that the compiler could (and should)

>> make in any appropriate case (with or without pragmas). The only effect it has

>>in Janus/Ada is to make some code illegal because of its rules. As such, I don't

>>want to spend much effort on it at any level -- certainly not for implementation

>>nor for the standard.

           

>If this is true, what happens if you just ignore the presence of D_I_C (or similar

>aspects) when deciding what to make it illegal, but otherwise generate exactly the

>same code?

For Janus/Ada, this would work fine. But that's because it makes no attempt to implement C.4 (it seemed impractical to be, given our library-based approach to implementing tasks and protected objects). The only thing that preelaboration does in Janus/Ada is suppress some of the subprogram elaboration check flags, since they're not needed (given that there cannot be any calls before elaboration). [I forgot about that optimization the other night.] Note that we did that in part because the handful of embedded customers we had needed to be able to restart their programs, and that is complicated if the data is pre-initialized. (We also provided a strict RAM/ROM separation.) The space needed to do a block initialize (the code is tiny, but the data has to be duplicated in the ROM) seemed usually to be more than the space needed to use the usual code to do the initialization (because some of the usual initialization uses loops, especially in array aggregates, and likely some of the components are uninitialized).

From a language perspective, my concern always was that an implementation that did implement C.4 would want to eliminate the elaboration thunk

(subprogram) associated with each preelaborated unit (since it can't do anything). [The supposedly empty subprogram and the calls waste space.] But if we require contracts to be checked, then that thunk probably has to continue to exist. Other implementers would have to tell us if that is a real problem.

>The only danger would be if the D_I_C code accessed something that isn't yet

>elaborated.  But that seems very unlikely, since the type used in the preelaborated

>package must itself be declared in a pure or preelaborated package, so anything the

>D_I_C code does can only be referring to data that has itself been preelaborated.

Right. I'm not completely convinced that that is enough to avoid elaboration errors - one still has to worry about accessing partial views early and the like. Freezing probably takes care of some of those problems, but does it catch them all??

Humm. Elaboration checks should fail if one calls a subprogram before its body has elaborated. And that does not necessarily happen immediately after the spec is elaborated. (Most compilers try to make that happen as much as possible, but it is not always possible, especially if "limited with"s make the dependencies circular.)

That is, you should get Program_Error if the following happens:

         -- Note: the body of P is not elaborated here.

Note that this cannot be a problem in the absence of D_I_C (or similar contracts), as no functions can be called in P.

I also note that this seems to be a problem with contracts in general, since this is even more likely to happen in "normal" packages. But that's not something we're going to worry about here.

But the main point is that if we allow elaboration to make calls to functions via contracts, then the optimization of not needing elaboration checks would have to be eliminated. (And that happens even if the checks are deferred, unless care is taken to ensure that they are deferred to a point when *all* preelaborated packages and their bodies and [package] subunits have been elaborated. That makes deferring the checks rather complicated, since they would need their own special thunk to be called at that much later point.)

>It seems that actually deferring the checks is never going to be necessary to avoid

>an elaboration error.

I don't agree, see above.

>Yes, there might be a function call in officially preelaborated code, but it is a call

>on something that has been already been elaborated.

The data has been elaborated, but the function body probably has not been.

And *it* could refer to something in the (package) body that has not been elaborated.

>RM C.4 establishes additional implementation requirements associated with preelaborated

>units, but we could clearly adjust that to take into account the possible run-time effects

>of a D_I_C aspect.  In fact, perhaps we can eliminate the permission to defer the checks

>based on this reasoning, and simply loosen the rules in C.4 to properly account for things

>like D_I_C.

I don't think this works (at least not by itself), for the reason given above.

But I point out that the cases that cause trouble here are corner cases, most of the functions here are simple tests. We could require that any functions called from a contract for a type with Preelaborable_Initialization be expression functions (or functions completed with expression functions inside the package specification). That would limit the capability of the functions somewhat, but also would eliminate the elaboration failure (and also any cases that could cause trouble). It is always safe to call expression functions (except within the package spec that declares them), so we could then do the other things you suggest (loosen the rules of C.4 and eliminate the permission to defer.

...

>I don't see the need for this.  Based on the above reasoning, ...

Which was wrong, so this is "if False". :-) To be fair, I thought you were right at first, but in thinking about it while writing this message I realized that you forgot the body check, and that makes all the difference.

>... it would seem we do not need to defer the checks, but rather need to

>loosen C.4 a bit in their presence.

   

For that to work, we have to insist that the "body" of the functions used in such contracts are declared in the same package spec as the type or subtype that has the contract. Luckily we can do that with expression functions. And then we don't need to have any permission at all (which will make Niklas happy). Although if other implementers would like a permission to defer the checks anyway, I wouldn't object (as noted in my previous message, the checks should never fail anyway).

Such a requirement is a bit incompatible (in that it might break Ada 2012 contracts in preelaborated packages, making them illegal; D_I_C is only an Ada 2022 contract, so we can assume that there aren't enough of those to worry about any incompatibility).


 

From: Tucker Taft

Sent: Wednesday, August 23, 2023  5:38 PM

...

>From a language perspective, my concern always was that an implementation

>that did implement C.4 would want to eliminate the elaboration thunk

>(subprogram) associated with each preelaborated unit (since it can't do

>anything). [The supposedly empty subprogram and the calls waste space.] But

>if we require contracts to be checked, then that thunk probably has to

>continue to exist. Other implementers would have to tell us if that is a

>real problem.

I don't think it is a real problem.  In AdaMagic (& Green Hills & PTC -- not sure about GNAT), if there is nothing to do, there is no preelaboration routine.  If there is something to do, then there is one.

 

>But the main point is that if we allow elaboration to make calls to

>functions via contracts, then the optimization of not needing elaboration

>checks would have to be eliminated. (And that happens even if the checks are

>deferred, unless care is taken to ensure that they are deferred to a point

>when *all* preelaborated packages and their bodies and [package] subunits

>have been elaborated. That makes deferring the checks rather complicated,

>since they would need their own special thunk to be called at that much

>later point.)

It does not seem so bad to have to get rid of an optimization under certain circumstances.

 

>>It seems that actually deferring the checks is never going to be necessary

>>to avoid an elaboration error.

>I don't agree, see above.

Yes, you have convinced me that elaboration check failures are possible.  Presumably pragmas Elaborate, Elaborate_All, and Elaborate_Body can be used in the normal way if this becomes a problem.

 

...

>>RM C.4 establishes additional implementation requirements associated with preelaborated

>>units, but we could clearly adjust that to take into account the possible run-time effects

>>of a D_I_C aspect.  In fact, perhaps we can eliminate the permission to defer the checks

>>based on this reasoning, and simply loosen the rules in C.4 to properly account for things

>>like D_I_C.

>I don't think this works (at least not by itself), for the reason given above.

I agree that elaboration checks might fail, which is a new situation for preelaborated code, but only if there are things like D_I_C, and that is visible at compile time, so it really just needs to suppress the optimization when a D_I_C appears.

>>... it would seem we do not need to defer the checks, but rather need to

>>loosen C.4 a bit in their presence.

>For that to work, we have to insist that the "body" of the functions used in

>such contracts are declared in the same package spec as the type or subtype

>that has the contract. Luckily we can do that with expression functions. And

>then we don't need to have any permission at all (which will make Niklas

>happy). Although if other implementers would like a permission to defer the

>checks anyway, I wouldn't object (as noted in my previous message, the

>checks should never fail anyway).

>

>Such a requirement is a bit incompatible (in that it might break Ada 2012

>contracts in preelaborated packages, making them illegal; D_I_C is only an

>Ada 2022 contract, so we can assume that there aren't enough of those to

>worry about any incompatibility).

Based on the above it seems to me we can allow implementations to do the checks at the normal time or defer the checks, but I don't think we need to allow implementations to ignore the checks.