Version 1.1 of ais/ai-00216.txt

Unformatted version of ais/ai-00216.txt version 1.1
Other versions for file ais/ai-00216.txt

!standard 03.08.01 (01)          99-03-23 AI95-00216/01
!class amendment 99-03-23
!status work item 99-03-23
!status received 99-03-23
!priority Medium
!difficulty Hard
!subject Unchecked Unions -- Variant Records With No Run-Time Discriminant
We propose to formalize the rules for the pragma Unchecked_Union, which is now supported by several Ada 95 compilers. The proposed rules are intended to be flexible enough to accommodate all reasonable uses, while preserving a modicum of safety.
The Ada 95 standard does not include a mechanism for mapping C unions to Ada data structures. This capability is important enough that several Ada 95 compilers have defined a method to support unions.
pragma Unchecked_Union(first_subtype_mark);
This pragma is intended for use with a type with a variant part (potentially with further nested variant parts), that is to be manipulated in C using union(s). The pragma specifies that the associated type should be given a representation that leaves no space for the discriminant(s) of the type. Furthermore, the effect of this pragma includes an implicit suppress of Discriminant_Check on the specified type, and an implicit convention of C (which may be overridden).
The specified type may have a non-variant part preceding the variant part, which would correspond to a C struct containing a union as its last component. A variant may have multiple components, which would correspond to a C union with a struct as one of its elements. A variant may have a nested variant part, which would correspond to a nested C union. The type may have multiple discriminants, to support the possibly nested unions being selected along different dimensions.
Normally the Ada type should have defaults for all discriminants. However, if the type is limited, defaults are not required.
If the type is non-limited, then all objects of the type, even if allocated in the heap (and hence normally considered constrained by the initial discriminant value), should be allocated the size C would allocate for the corresponding struct/union, which will be at least the maximum size required for any discriminant value. This is because any whole-object assignments performed to or from such an object by the C code will generally involve this maximum size, even if they preserve the (conceptual) discriminant value.
If the type is limited, the "exact" size may be allocated, determined by the value of the discriminant specified when the object is created. It is erroneous for a whole-object assignment to be performed to or from an object of a limited unchecked_union type which is passed to C code, because it would generally read or write past the end of the object.
A discriminant of an object of an unchecked_union type may not be read, except in a default initialization expression for a component. The discriminant(s) must be specified (explicitly or implicitly) when an object of the type is created, even though its (their) value is not stored, to enable appropriate default initialization of the appropriate variant(s), or to determine which fields should appear in a record aggregate.
Constrained subtypes of an unchecked_union type are permitted, as this may be necessary to properly specify the discriminant value for a variable of the type. It is erroneous to perform any operation (in C or Ada) that would have failed a discriminant check had the discriminant been present at run-time.
Within the definition of an unchecked_union type, the discriminant(s) may not be used in a constraint in a component_definition, unless the type of the component is itself an unchecked_union type. This is to ensure that the size of the component does not depend on the value of the discriminant.
In an instantiation of a generic, if the actual type is an unchecked union, then the formal type must not have known discriminants, to avoid contract violations involving discriminant references in the body of the generic.
To avoid generic contract violations, if the type is non-limited, both equality and assignment are permitted. Both are defined in terms of block operations operating on all bits of the representation. A warning on uses of equality would be appropriate, given the possiblity of uninitialized gaps in the representation.
Given the C type:
struct sym {
int id; char *name; union {
struct { struct sym *obj_type; int obj_val_if_known; } obj; struct { struct sym *pkg_first_component; int pkg_num_components; } pkg;
} u;
This would map to the following unchecked_union type in Ada:
type Sym; type Sym_Ptr is access Sym;
type Sym_Kind_Enum is (Obj_Kind, Pkg_Kind);
type Sym(Kind : Sym_Kind_Enum := Sym_Kind_Enum'First) is record
Id : Integer := Assign_Id(Kind); Name : C_Ptr; case Kind is when Obj_Kind => Obj_Type : Sym_Ptr; Obj_Val_If_Known : Integer := -1; when Pkg_Kind => Pkg_First_Component : Sym_Ptr; Pkg_Num_Components : Integer := 0; end case;
end record; pragma Unchecked_Union(Sym);
Several Ada 95 compilers now support a pragma Unchecked_Union for specifying that the discriminant of a variant record should not be present at run-time, thereby matching a C union. However, these compilers differ in the legality rules they enforce relative to the type.
This proposal is designed to allow what in C is essentially one (named) type definition, to similarly become one type definition in Ada. It is quite common to use anonymous unions and structs nested within one another in C. In Ada, the natural mapping for this is non-variant and variant parts, and nested variants. The need to eliminate the storage for the discriminant exists for these more complex uses of "union" in C just as much as it exists for a very simple use of "union." Hence, we propose to allow unchecked_union types to include non-variant parts along with variant parts, to include multiple components per variant, and to support nested variants, to maximize the likelihood that there is a natural mapping possible between the C type structure and the Ada type structure.
We have also defined rules that should eliminate generic contract model violations related to unchecked_unions, as this seems important if such types are going to be used in relatively portable C interfacing code with compilers that may share generics.
We have proposed a distinction in the handling of limited and non-limited unchecked_union types. Basically, objects of a limited unchecked union type are presumed not to change their (conceptual) discriminant during their lifetime, whereas objects of a non-limited type can. Furthermore, block assignment to or from objects of a non-limited unchecked union type are expected to involve the "max size" for the type, whereas such block assignments are presumed not to occur for limited unchecked_union types. This permits the more efficient use of storage by using limited unchecked_union types in cases where the size varies significantly between the different variants. This corresponds to the case in C where the programmer uses extra effort to allocate only what space is needed for the particular element of the union being created, knowing that the object's "kind" (however that might be defined in C) does not change after the object is created.

Randy Brukardt  99-03-29

I've reformatted Tucker's submission into the format discussed at
the recent ARG meeting. Note that I haven't made any of the changes
considered at that meeting.


Questions? Ask the ACAA Technical Agent