Version 1.3 of ais/ai-00383.txt

Unformatted version of ais/ai-00383.txt version 1.3
Other versions for file ais/ai-00383.txt

!standard B.03(42)          04-08-31 AI95-00383/00
!standard B.03(70)
!class binding interpretation 04-08-31
!status received 04-08-31
!priority Low
!difficulty Medium
!subject Unconstrained arrays and C interfacing
!summary
!question
The language requires support for C-compatible unconstrained array types (e.g. B.3(42) as applied to Interfaces.C.Char_Array). The correspondence described in B.3(70) applies to both constrained and unconstrained array types. This works fine in the Ada-calling-C direction, but not in the case of Ada-called-by-C. In the latter case, Ada is passed no information about the bounds of the array. How are Ada constructs which require bounds information supposed to work?
!wording
!discussion
Consider the following example:
with Interfaces.C; package Foo is subtype Char_Array is Interfaces.C.Char_Array; Buffer : Char_Array (1 .. 10) := "1234567890"; Renamed_Slice : Char_Array renames Buffer (3 .. 7);
procedure Proc1 (X : Char_Array); pragma Export (C, Proc1, Link_Name => "proc1"); end Foo;
package body Foo is procedure Proc1_Callback (X : Char_Array); pragma Import (C, Proc1, Lin_Name => "proc1_callback"); -- -- Written in C, this routine takes a char* argument, which it passes -- as the argument of a call to proc1.
procedure Assert (Condition : Boolean) is Test_Failed : exception; begin if not Condition then raise Test_Failed;
end if; end Assert;
procedure Proc1 (X : Char_Array) is use type Char_Array; use type Interfaces.C.Size_T; begin Assert (X'First = Renamed_Slice'First); Assert (X'Last = Renamed_Slice'Last); Assert (X = Renamed_Slice); end Proc1; begin Proc1 (Renamed_Slice); Proc1_Callback (Renamed_Slice); end Foo;
The current language rules don't provide any justification for rejecting this example, but it is not clear how it can be implemented.
Compare this situation with the treatment of missing discriminants for Unchecked_Union types (AI-216). Discriminant checks are suppressed and each construct which would require accessing a missing discriminant is either statically illegal or is defined to raise Program_Error at runtime. It seems that an analogous set of rules is needed to handle this case. Just as "inferable discriminants" are defined for Unchecked_Union types, a corresponding definition of "inferable bounds" might make sense; on the other hand, this might not be worth the definitional complexity.
A call (from Ada) to a convention-C function with an unconstrained array result presents much the same problem; the caller does not know the bounds of the function result.
--!corrigendum
!example
!ACATS test
!appendix

From: Randy Brukardt
Sent: Tuesday, August 31, 2004  6:02 PM

Steve wrote:

> The language requires support for C-compatible unconstrained array types
> (e.g. B.3(42) as applied to Interfaces.C.Char_Array). The correspondence
> described in B.3(70) applies to both constrained and unconstrained array types.
> This works fine in the Ada-calling-C direction, but not in the case of
> Ada-called-by-C. In the latter case, Ada is passed no information about the
> bounds of the array. How are Ada constructs which require bounds information
> supposed to work?

Obviously, they don't. Since this is just Implementation Advice anyway, it can
be ignored if it says something that is nonsense. Certainly, that is true in
this case.

We can also apply Dewar's rule here - the language says something stupid (C
certainly can't call unconstrained arrays).

Janus/Ada just rejects any pragma Import or Export that it doesn't know how to
code generate. While I followed the IA as much as possible, I didn't sweat
anything that I couldn't figure out... Still, the IA should be fixed (thus I
opened an AI as noted above).

...
>Compare this situation with the treatment of missing discriminants for
>Unchecked_Union types (AI-216). Discriminant checks are suppressed and each
>construct which would require accessing a missing discriminant is either
>statically illegal or is defined to raise Program_Error at runtime.
>It seems that an analogous set of rules is needed to handle this case.
>Just as "inferable discriminants" are defined for Unchecked_Union types,
>a corresponding definition of "inferable bounds" might make sense; on the other
>hand, this might not be worth the definitional complexity.

Certainly not. We should just not allow such declarations; they cannot mean
anything useful.

>A call (from Ada) to a convention-C function with an unconstrained array result
>presents much the same problem; the caller does not know the bounds of the
>function result.

Right; this is the same problem.

It should be noted that a legality rule would be OK here, because the rule
would not need to depend on the representation of the unconstrained array:
*all* unconstrained arrays should be banned in parameters of Export to C (and
most other languages, too) and function returns of Import from C. But this rule
probably should be IA as well, because we don't want to prevent an
implementation from doing this if they can figure out some meaning for it.

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

From: Tucker Taft
Sent: Tuesday, August 31, 2004  8:21 PM

We support unconstrained arrays as parameters in C conventions
by giving the array the maximum bounds (i.e. index_subtype'first ..
index_subtype'last).  Clearly there needs to be some other parameter
which conveys the length, or some convention such as null termination.

We have one customer who has requested a new convention, which
we call "C_With_Array_Len" which adds an extra parameter in
the C interface corresponding to the length of the array,
so that the bounds become index_subtype'first .. index_subtype'first +
len-param - 1.  If the array is multidimensional, a separate
"len" parameter is added for each dimension.

I'm not recommending this to be standardized, but it does indicate
that some people are uncomfortable with our "standard" max-range
approach.

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

From: Robert Dewar
Sent: Wednesday, September 1, 2004  8:39 AM

Related to this is the issue that Address_To_Access conversions
cannot work with unconstrained arrays.

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

From: Randy Brukardt
Sent: Wednesday, September 1, 2004  3:46 PM

For the record, that's not strictly true; it works fine in Janus/Ada. But
that's because we ignore Implementation Advice 13.3(14) [following it would
have been incompatible with our Ada 83 compilers, and would have caused
problems with some run-time code - as then there would be no way to access the
array descriptors. It also would mean that it would be impossible to use an
Address Clause with a constrained at creation array object.]. ('Address and
'Access in Janus/Ada always point at the top-level object; for an array of size
not known at compile-time, that's the array descriptor, not the data. The same
is true for a component constrained by a discriminant.)

But it's clearly not portable (and contrary to the mentioned IA), so it would
make sense to discuss this case as well in the AI.

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

From: Robert Dewar
Sent: Wednesday, September 1, 2004  4:17 PM

> For the record, that's not strictly true; it works fine in Janus/Ada. But
> that's because we ignore Implementation Advice 13.3(14)

Ouch! I would guess most of our customers big programs depend on that IA :-)

> But it's clearly not portable (and contrary to the mentioned IA), so it
> would make sense to discuss this case as well in the AI.

Well if making something work means breaking some (really important in my
view) IA, that's definitely an issue :-)

What we do is to generate a warning that To_Pointer cannot be expected
to be useful in this case:

      3.    package A is new System.Address_To_Access_Conversions (String);
            |
         >>> warning: in instantiation at s-atacco.ads:45
         >>> warning: Object is unconstrained array type
         >>> warning: To_Pointer results may not have bounds

In fact in GNAT the "may" is here because if the address was originally
obtained by taking the address of a string, then the bounds may actually
be where they are expected to be :-)

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

From: Robert Dewar
Sent: Wednesday, September 1, 2004  4:24 PM

> We have one customer who has requested a new convention, which
> we call "C_With_Array_Len" which adds an extra parameter in
> the C interface corresponding to the length of the array,
> so that the bounds become index_subtype'first .. index_subtype'first +
> len-param - 1.  If the array is multidimensional, a separate
> "len" parameter is added for each dimension.
>
> I'm not recommending this to be standardized, but it does indicate
> that some people are uncomfortable with our "standard" max-range
> approach.

Actually I think this is something that would be worth while
semi-standardizing. GNAT tends to absorb such things anyway,
so I think it might be worth including this as implementation
advice. I trust it won't get too complicated (*).

An alternative to having an extra parameter is to pass a descriptor.
This is what is done in VMS for pass by descriptor.

It also seems a shame to assume index_subtype'first, why not pass
the bounds properly?

(*) We just finished implementing the full blown Unchecked_Union.
What a nightmare! And the sad thing is that our best guess is
that none of the fancy extra features will get actually used :-(

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

From: Tucker Taft
Sent: Wednesday, September 1, 2004  4:48 PM

> It also seems a shame to assume index_subtype'first, why not pass
> the bounds properly?

We are trying to present an interface that means something
to a C programmer.  Arrays in C only have a length.  The
low bound is always 0.  In Ada, the programmer gets to
choose what is the more natural low bound (0, 1, whatever),
but the C programmer needn't be bothered with this
nicety.

> (*) We just finished implementing the full blown Unchecked_Union.
> What a nightmare! And the sad thing is that our best guess is
> that none of the fancy extra features will get actually used :-(

In our C code, we have many struct/union trees that match what
the "full blown" Unchecked_Union is designed to support.
For what that is worth...

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

From: Robert Dewar
Sent: Wednesday, September 1, 2004  5:14 PM

Tucker Taft wrote:

> In our C code, we have many struct/union trees that match what
> the "full blown" Unchecked_Union is designed to support.
> For what that is worth...

But C unions don't allow more than one component in each variant!

Also, it is not the data layout that is the issue, it is all the
special casees of allowing things like equality when you can figure
out the discriminants.

Did you implement this feature fully yet?

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

From: Pascal Leroy
Sent: Thursday, September 2, 2004  7:38 AM

If we are in the business of fixing A_To_A_Conversion, I would be in favor
of making the access type a formal type (I know, for compatibility a new
generic would be required).  It is totally dumb that each instantiation of
this generic creates a collection, for which you cannot specify the
storage size.

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

From: Robert A. Duff
Sent: Thursday, September 2, 2004  7:56 AM

Mea Culpa.  I wrote that section.  I did it that way on purpose, but
after having used the thing a few times, I realized it is, as you say,
"totally dumb".  But I don't think it's important enough to bother
fixing.

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

From: Robert Dewar
Sent: Friday, September 3, 2004  3:14 AM

Indeed! This is a fundamental inconsistency which should be fixed. moreover
you cannot specify a storage pool, which is even worse.

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

From: Tucker Taft
Sent: Friday, September 3, 2004  7:19 AM

I'm not sure I understand the concern here.  The generic
is intended to provide an access value given an address.
It was never expected that such an access value would
be considered equivalent to an access value created by
an allocator.  This generic is more like GNAT's
'Unrestricted_Access. So what possible meaning would
there be for the storage size or storage pool, since those
attributes are relevant only for allocators (and unchecked
deallocation)?

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

From: Tucker Taft
Sent: Friday, September 3, 2004  8:20 AM

I might add that anonymous access types, which we are
now allowing in more contexts, have the same property
of not allowing the specification of storage size or
storage pool.  So this all sounds like a tempest
in a teapot to me.  Do people really have a bunch
of these A_To_A instantiations all over the place?

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

From: Robert A. Duff
Sent: Friday, September 3, 2004  8:58 AM

I agree that it's a tempest in a tea pot.

I think the issue is not Storage_Pool per se.  The issue is that if you
have an access type, and you want to convert an Address value to that
type, you have to call the To_Pointer function from the instance, and
then convert *that* to your access type, using a normal type conversion.
It would be slightly cleaner to be able to convert directly.

So why don't you just use the access type declared in the instance of
Address_To_Access_Conversions?  I think Pascal was saying, "Because I
want a Storage_Size clause", and Robert was saying, "Yeah, and because I
want a Storage_Pool clause."  Another obvious reason is that your access
type belongs in a package spec, and the instance belongs in the body.

I still say: it's not broken enough to fix.  Just put in the silly extra
type conversion.  One hopes not to use this feature very often!

By the way, why would you want to use Address_To_Access_Conversions to
go the other direction?  I mean, isn't X.all'Address sufficient?
(I usually don't want nulls in these cases...)

P.S. The reason I didn't do it Pascal's way in the first place, is that
I imagined the only usage would be for "peek/poke" functionality.  That
is, I thought you would always do something like this:

    Thing: Integer renames To_Pointer(Some_Address).all;

    Thing := Thing + 1;

with an immediate .all, and you don't care what the access type is.
Declaring one and passing it to the instantiation is just annoying
verbosity in this case.
(In fact, you want an anonymous access type!)

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

From: Robert Dewar
Sent: Friday, September 3, 2004  3:54 PM

Well I think it would be reasonable to have a note in the RM noting
the case that is unlikely to work, or rather unlikely to work in
anything approximating a portable manner.

> I think the issue is not Storage_Pool per se.  The issue is that if you
> have an access type, and you want to convert an Address value to that
> type, you have to call the To_Pointer function from the instance, and
> then convert *that* to your access type, using a normal type conversion.
> It would be slightly cleaner to be able to convert directly.

Can you say what you mean here, you can't just go converting access
types when storage pools are involved???

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

From: Randy Brukardt
Sent: Friday, September 3, 2004  7:12 PM

While I generally agree with you and Pascal on this topic, I don't understand
the above. There is no restriction on converting between general access types
as long as they designate the same type. The only problem comes if you call
Unchecked_Deallocation on an access from another pool; that's defined to be
erroneous in 13.11.2(16).

Even if you had an A_to_A that allowed an access type with a pool, I would hope
that using Unchecked_Deallocation on the result would still be erroneous. (It
certainly wouldn't be meaningful unless it was previous allocated from that
pool - in which case, why the heck do you need A_to_A??) So there isn't going
to be any semantic difference here.

The real issue to me is that we expanded the use of anonymous access types
specifically so we could get rid of unnecessary explicit conversions, as
they're confusing to the reader (a type conversion is supposed to indicate
something happening). This is another source of unnecessary conversions, and it
would make sense to get rid of them here as well.

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

From: Robert A. Duff
Sent: Friday, September 3, 2004  7:30 PM

> Well I think it would be reasonable to have a note in the RM noting
> the case that is unlikely to work, or rather unlikely to work in
> anything approximating a portable manner.

You mean the access-to-unc-array case?  Yeah, a note seems reasonable.

> > I think the issue is not Storage_Pool per se.  The issue is that if you
> > have an access type, and you want to convert an Address value to that
> > type, you have to call the To_Pointer function from the instance, and
> > then convert *that* to your access type, using a normal type conversion.
> > It would be slightly cleaner to be able to convert directly.
>
> Can you say what you mean here, you can't just go converting access
> types when storage pools are involved???

The package as currently defined:

      generic
          type Object(<>) is limited private;
      package System.Address_To_Access_Conversions is
         pragma Preelaborate(Address_To_Access_Conversions);

         type Object_Pointer is access all Object;
         function To_Pointer(Value : Address) return Object_Pointer;
         ...
      end System.Address_To_Access_Conversions;

I thought Pascal's proposal was to change it to:

      generic
          type Object(<>) is limited private;
          type Object_Pointer is access all Object;
      package System.Address_To_Access_Conversions is
         pragma Preelaborate(Address_To_Access_Conversions);

         function To_Pointer(Value : Address) return Object_Pointer;
         ...
      end System.Address_To_Access_Conversions;

That is, make the pointer type into a generic formal.

With the current language, one can do this:

    package Things is
        type Thing is ...
        type Thing_Ptr is access all Thing;
        for Thing_Ptr'Storage_Pool use ...;
        ...
    end Things;

    with System.Address_To_Access_Conversions;
    package body Things is
        package Conv is new System.Address_To_Access_Conversions(Thing);

        function F(...) return Thing_Ptr is
        begin
            return Thing_Ptr(Conv.To_Pointer(Some_Address));

With Pascal's proposal, the body becomes:

    with System.Address_To_Access_Conversions;
    package body Things is
        package Conv is new
            System.Address_To_Access_Conversions(Thing, Thing_Ptr);

        function F(...) return Thing_Ptr is
        begin
            return Conv.To_Pointer(Some_Address);

That is, the type conversion to Thing_Ptr is eliminated.  Either way, Thing_Ptr
can have a Storage_Pool or Storage_Size clause.  And either way, the programmer
had better ensure that Some_Address "makes sense" as a value of type Thing_Ptr.
And either way, if you try to use Unchecked_Dealloc, you better make
sure it originally came from the same pool.

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

From: Robert A. Duff
Sent: Friday, September 3, 2004  7:41 PM

> While I generally agree with you and Pascal on this topic, I don't
> understand the above. There is no restriction on converting between general
> access types as long as they designate the same type. The only problem comes
> if you call Unchecked_Deallocation on an access from another pool; that's
> defined to be erroneous in 13.11.2(16).

Right.

> Even if you had an A_to_A that allowed an access type with a pool, I would
> hope that using Unchecked_Deallocation on the result would still be
> erroneous.

Yes.

>... (It certainly wouldn't be meaningful unless it was previous
> allocated from that pool - in which case, why the heck do you need A_to_A??)

If you're playing games with storage pools, you might have a function
that allocates some storage, and returns a pointer to that storage plus
some offset.  And deallocate might need to recover the original storage
pointer by subtracting that offset.  I've done something like that in
low-level storage-pool code, where *this* storage pool is allocating
hunks of storage from an underlying storage pool.

> So there isn't going to be any semantic difference here.

Right.

> The real issue to me is that we expanded the use of anonymous access types
> specifically so we could get rid of unnecessary explicit conversions, as
> they're confusing to the reader (a type conversion is supposed to indicate
> something happening). This is another source of unnecessary conversions, and
> it would make sense to get rid of them here as well.

Right.  So should it be:

      generic
          type Object(<>) is limited private;
      package System.Address_To_Access_Conversions is
         pragma Preelaborate(Address_To_Access_Conversions);

         function To_Pointer(Value : Address) return access all Object;
         ...                                         ^^^^^^^^^^^^^^^^^
      end System.Address_To_Access_Conversions;

where the result of To_Pointer can be *implicitly* converted to whatever
appropriate access type you like?  Or something like that?

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

From: Pascal Leroy
Sent: Monday, September 6, 2004  4:00 AM

Well, it's nice to reduce the number of implicit conversions, but it
doesn't solve the problem I had in mind.

Say that I am getting an address from some low-level mechanism, e.g.
calling a C library.  I want to convert this address into an Ada
access-to-record, to access individual fields in a type-safe manner.
There are various useful properties that I am unable to express in Ada
because the silly A_To_A_Conversion insists on declaring the access type
itself:

1 - I am not going to change the allocated structure; I'd like to have an
access-to-constant.
2 - I am not going to do any allocation on the Ada side; I'd like to have
storage size of 0.
3 - I may be doing allocations/deallocations on the Ada side, but they
better go through the storage management mechanism provided by the C
library, lest plague and pestilence ensue; I'd like to specify a storage
pool.

All three would be possible to express if the generic took an access type
as a parameter (OK, we would need two generics for access-to-constant and
access-to-variable).  And please don't tell me that I can just convert the
access type exported by the instantiation to a user-declared one with the
right properties, because that doesn't give me any additional safety: I
cannot prevent code from writing/allocating/deallocating through the type
exported by the instantiation.

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

From: Robert A. Duff
Sent: Monday, September 6, 2004  8:48 AM

> All three would be possible to express if the generic took an access type
> as a parameter (OK, we would need two generics for access-to-constant and
> access-to-variable).

The above are all true.  I just don't think it's important enough to
fix.

>...And please don't tell me that I can just convert the
> access type exported by the instantiation to a user-declared one with the
> right properties,...

Too late.   ;-)  I already told you that.

>...because that doesn't give me any additional safety: I
> cannot prevent code from writing/allocating/deallocating through the type
> exported by the instantiation.

Well, you can work around that by instantiating in a fairly invisible
place.  E.g.

    function To_Ptr(X: System.Address) return Some_Ptr is
        package Instance is new A_To_A_Conversions(...);
    begin
        return Some_Ptr(Instance.To_Pointer(X));
    end To_Ptr;

Or leave out Some_Ptr() if the conversion is implicit.

These annoyances would bother me more if A_To_A_C were something I use
every day.  I must admit that I've used Unchecked_Conversion instead,
sometimes.

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

From: Robert I. Eachus
Sent: Monday, September 6, 2004  11:46 AM

"Fixing" this issue would in my mind be a bad idea.  It would add an
unnecessary upward incompatibility to the language. But adding separate
children of System that takes access parameters may be worth doing.  The
question for implementors is whether or not adding the the following
package to the standard would require much if any work:

 generic
    type Object(<>) is limited private;
    type Object_Pointer is access all Object;
 package System.Address_To_Named_Access_Conversions is
    pragma Preelaborate(Address_To_Named_Access_Conversions);

    function To_Pointer(Value : Address) return access all Object;
    function To_Address(Value : Object_Pointer) return Address;

    pragma Convention(Intrinsic, To_Pointer);
    pragma Convention(Intrinsic, To_Address);

 end System.Address_To_Named_Access_Conversions;

 generic
    type Object(<>) is limited private;
    type Object_Pointer is access constant Object;
 package System.Address_To_Constant_Access_Conversions is
    pragma Preelaborate(Address_To_Constant_Access_Conversions);

    function To_Pointer(Value : Address) return access constant Object;
    function To_Address(Value : Object_Pointer) return Address;
    pragma Convention(Intrinsic, To_Pointer);
    pragma Convention(Intrinsic, To_Address);

 end System.Address_To_Constant_Access_Conversions;

My guess is that the work required would be mostly cut and paste (as would the
work in the RM), so it may be easier to add these packages than to continue the
debate.

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

From: Robert A. Duff
Sent: Monday, September 6, 2004  12:33 PM

By "fixing", I meant "adding separate children...", which is exactly
what Pascal suggested.  But note that Pascal or any other programmer
can implement the desired functionality in plain portable Ada 95:

 generic
    type Object(<>) is limited private;
    type Object_Pointer is access all Object;
 package System.Address_To_Named_Access_Conversions is
    pragma Preelaborate(Address_To_Named_Access_Conversions);

    function To_Pointer(Value : Address) return Object_Pointer;
    function To_Address(Value : Object_Pointer) return Address;

    pragma Inline(To_Pointer);
    pragma Inline(To_Address);

 end System.Address_To_Named_Access_Conversions;

 with Address_To_Access_Conversions;
 package System.Address_To_Named_Access_Conversions is

    package A_To_A is new Address_To_Access_Conversions(Object);

    function To_Pointer(Value : Address) return return Object_Pointer is
    begin
        return Object_Pointer(A_To_A.To_Pointer(Value));
    end To_Pointer;

    function To_Address(Value : Object_Pointer) return Address is
    begin
        return Value.all'Address;
OR (if you want to allow nulls):
	return To_Address(A_To_A.Object_Pointer(Value));
    end To_Address;

 end System.Address_To_Named_Access_Conversions;

and similarly for the access-constant version.

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

From: Robert Dewar
Sent: Monday, September 6, 2004  12:50 PM

This is not portable Ada, you can't go adding children to
System yourself!

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

From: Robert A. Duff
Sent: Monday, September 6, 2004  2:08 PM

Got me!

I forgot to delete "System." from the example.  I did remember to change
pragma Intrinsic to pragma Inline.  ;-)

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


Questions? Ask the ACAA Technical Agent