AI22-0099-1

!standard 4.6(24.16/2)                                        24-09-04  AI22-0099-1/03

!class binding interpretation 24-03-04

!status Amendment 1-2022 24-05-02

!status WG9 Approved 24-10-10

!status ARG Approved 13-0-1  24-05-02

!status work item 24-03-04

!status received 24-03-04

!submitter Randall Brukardt

!priority Low

!difficulty Easy

!qualifier Omission

!subject Object_Size and conversions of access-to-object types

!summary

Conversions between access-to-object types are not allowed if a non-confirming value for Object_Size is specified for one or both of the designated types, and the sizes do not match.

!issue

Static matching includes a requirement that the Object_Size of a subtype matches (or is confirming for both subtypes). This requirement exists in part so that conversions between different access-to-object types designating different subtypes are only allowed when the Object_Size is compatible.

However, for access types that designate discriminated record types, we allow conversions between types that have designated subtypes that do not statically match so long as at least one of the subtypes is unconstrained. (See AI22-0086-1 for more on this topic.) That means in that case, the Object_Size matching requirement has been lost. Should it be required for conversions? (Yes, unless both are confirming.)

!recommendation

For an access-to-object conversion to be legal, the Object_Sizes of the designated subtypes either have to match or both be confirming. This applies even when other static matching requirements are not applied.

!wording

Modify 4.6(24.16/6): (as modified by AI22-0086-1)

!discussion

Notes on the wording:

The wording was split into sub-bullets to avoid having a run-on bullet. The wording for the Object_Size bullet is crafted to work if unconstrained and constrained record subtypes have different confirming Object_Size values. If either Object_Size is specified to a non-confirming value, then the Object_Size values must match (even if the other Object_Size is confirming or unspecified).

It might be better to somehow factor out the static matching properties that we want to apply here, rather than duplicating the rules in another place. That would require defining yet another term, say “statically similar”:

Two subtypes of the same type are statically similar if all predicate specifications that apply to them come from the same declarations, and the value of Object_Size (see 13.3) shall be the same if Object_Size has been specified to have a nonconfirming value for either subtype.

Then we can use “statically similar” in both this 4.6(24.16/6) rule and also in the definition of static matching in 4.9.1(2/5). This is especially appealing if we later discover that the Nonblocking and Global rules from static matching also need to apply here (then the fix would be easy, just move them to “statically similar”). I didn’t do this as it requires quite a bit more change (and a new term) than the simple fix given above. Note that this formulation requires a slightly different rule for Object_Sizes as we cannot assume here that the constraints match (that is assumed for static matching).


 

A reminder on why we need the Object_Size of designated subtypes to match.

The purpose of Object_Size is to provide the default size (in the absence of other representation aspects) for an object of the subtype. In particular, it specifies the size of all aliased objects of the subtype (see 13.3(58.2/5)). Of course, all objects designated by an access type have to be aliased, either as aliased stand-alone objects, or as allocated objects.

A compiler should be able to assume that any designated object is at least as large as the Object_Size of its nominal subtype. For instance, if S’Object_Size = 32, then accessing a dereference of an access-to-S type A with a 32-bit instruction should always be appropriate. If we have S2 which is a subtype of S with S2’Object_Size = 16, then converting a value of an access-to-S2 type A2 to type A should not be allowed, as A2 might designate an object that is smaller than 32 bits (and if A was allowed to designate that object, then we might be writing bits that are owned by some other object). This means that we cannot allow a conversion between designated subtypes where the Object_Size of the source is smaller than that of the target.

Since we always want conversions to work in both directions, this boils down to a requirement that the Object_Size matches. We exclude confirming Object_Sizes from this requirement both for compatibility (code that does not use Object_Size, which is new in Ada 2022, should never be rejected for this reason) and as compilers have to make choices that allow access conversions.


 

Ada does not require/recommend any non-confirming Object_Sizes to be supported for record types. Moreover, the meaning of Object_Size for indefinite types is left implementation-defined. This has led to some confusion as to whether we need a rule here.

We want to encourage implementations to support specifying as many representation aspects as makes sense for their implementation. We do not (and cannot) know exactly what may make sense for a particular implementation (it is likely to vary a lot). What we need to do, however, is to ensure that whatever choices an implementation makes are reflected in the rules.

It seems likely that at least some implementations will support Object_Size on unconstrained but definite record types. That means that this rule will come into play for them, and for the reasons previously discussed, we need to ensure that non-confirming Object_Size values match. We cannot have a situation where the compiler expects to be able to write 64-bits (because that is the Object_Size of the designated subtype) but the actual designated object only has 48-bits.

If an implementation did support specifying Object_Size on some indefinite record subtype, one expects that the meaning would be similar to that for other kinds of subtypes. We could have some sort of exception for indefinite record subtypes, but note that that would be different than static matching. And it would add a complication for a case that may never come up (we expect that most implementations will not allow specifying Object_Size on indefinite subtypes).

!corrigendum 4.6(24.16/2)

@drepl

@xbullet{the designated type shall be discriminated in its full view and unconstrained in any partial view, and one of the designated subtypes shall be unconstrained;}

@dby

@xbullet{the designated type shall be discriminated in its full view and unconstrained in any partial view, further:}

@xinbull{at least one of the designated subtypes shall be unconstrained;}

@xinbull{any predicate aspect specification that applies to one of the designated subtypes shall also apply to the other; and}

@xinbull{the value of Object_Size (see @ref{13.3}) shall be the same for the designated subtypes if either has specified Object_Size to have a nonconfirming value;}

!ACATS test

An ACATS B-Test could be constructed to check this rule, but it necessarily would have to be conditional on whether or not the implementation allowed specifying Object_Size on definite but unconstrained record subtypes.

!appendix

From: Randy Brukardt

I often use a “belt-and-suspenders” approach to ensuring that I get the correct representation (particularly when interfacing). I think it is important that whatever rule we choose does not prevent conversions when the user specifically specified Object_Size to ensure that they are the same.

For instance, consider the following variant record type:

     type Rec (B : Boolean := True) is record

         case B is

             when True =>  Id : Character;

             when False => Data : Long_Integer;

        end case;

     end record with Object_Size => 64;

     subtype True_Rec is Rec(True) with Object_Size => 64;

     type Acc_Rec is access all Rec;

     type Acc_TRec is access all TRec;

     A : Acc_TRec := new Trec(True, ‘A’);

     B : Acc_Rec := Acc_Rec(A); -- (1)

Here, the programmer is ensuring that the designated subtypes are compatible by specifying Object_Size on both. So the conversion at (1) should succeed. The programmer would be pretty upset if they had explicitly specified the Object_Sizes and they still got a rejection of the conversion because of inappropriate Object_Sizes!

On at least some implementations, the default Object_Size of True_Rec will be smaller than 64 (16 bits are enough to hold any value of this subtype). So the specification of Object_Size here could be nonconfirming. On the other hand, for (sub)type Rec, the Object_Size might be confirming. So we cannot assume that both (or neither) are confirming; it is possible that one is confirming and the other is not.