!standard 13.11(21.5/3) 18-01-11 AC95-00299/00 !class Amendment 18-01-11 !status received no action 18-01-11 !status received 17-10-14 !subject Maximum alignment for an allocator !summary !appendix !topic Maximum alignment for an allocator !reference Ada 2012 RM13.11 !from Victor Porton 17-10-14 !keywords allocator alignment !discussion The problem description: It is impossible to properly implement an allocator through a C function (such as raptor_alloc_memory() from Raptor C library) which allocates a struct and returns the pointer to the allocated struct. It is because RM13.11(21.5/3) "The Alignment parameter is a nonzero integral multiple of D'Alignment..." If it were "The Alignment parameter is equal to D'Alignment", then we would be able just to check (in Allocate procedure implementation) that pragma Assert(Dummy_Record'Alignment mod Alignment = 0); -- where Dummy_Record is an arbitrary C-convention record -- (as all C records have the same alignment reqs) (but this is not enough in the modern version of the RM). So Alignment parameter may be arbitrarily big (subject to 'Max_Alignment_For_Allocation) and the C function alignment may not conform to it. Proposed solutions: We can add to System.Storage_Pools: function Max_Alignment(Pool : Root_Storage_Pool) return Storage_Elements.Storage_Count is (0); Then allocator for a type T with T'Alignment would ensure (and otherwise raise Program_Error) that "Max_Alignment(T'Storage_Pool) mod T'Alignment = 0" and request alignment which is a divisor of Max_Alignment(T'Storage_Pool). The deficiency of the above solution is that it introduces a new primitive operation which may possibly clash with user-defined functions. An alternative solution: Add 'Max_Alignment pool attribute and the following syntax for a pool Pool: for Pool'Max_Alignment use . *************************************************************** From: Randy Brukardt Sent: Friday, October 20, 2017 8:43 PM ... > It is impossible to properly implement an allocator through a C > function (such as raptor_alloc_memory() from Raptor C > library) which allocates a struct and returns the pointer to the > allocated struct. > > It is because RM13.11(21.5/3) "The Alignment parameter is a nonzero > integral multiple of D'Alignment..." This message happened to appear while the ARG was waiting for another message during our recent meeting, and everyone (to a man) said that they were confused. We didn't discuss it further (you won't find it in the minutes). Having read the comp.lang.ada discussion, I think you needed to better explain your use case. Certainly, one can always get a larger alignment by allocating extra memory and aligning the result. Dmitry explained that technique on comp.lang.ada. But you said that won't work, "because the C library I am writing bindings for may try to free an object allocated by me (or I my need to free an object allocated by the library)." That seems to me to be an exceedingly bad idea. You are guaranteed to run into issues if you try to do that (Ada and C using different representations, etc.). I've always kept all allocation/deallocation of my libraries on one side or the other. And if it is the C library that is doing the allocation/deallocation, I've just used subprogram calls for implementing that, never a Storage_Pool. I don't think there ever was any intention that a storage pool would work seamlessly with a C function; they're intended for allocating *Ada* objects. Keep in mind that all C interfacing is by definition implementation-defined, since it depends heavily on the C compiler. There's only so much portability that can be achieved in such interfacing; it can never be 100%. Moreover, one usually puts the Ada versions of things into the Ada thick binding. The C thin binding by definition is never going to be very usable. (It can't use exceptions, or default parameters, or dispatching, or stronger typing than the C, and so on. We did all of those things in the think Claw libraries.) From your questions, you seem to be trying to implement a single level binding and make it very usable. That's a losing game; an Ada library should look like it is designed for Ada, not for C. And if you want to stay as close as possible to the original C, then just do that and don't try to get clever. BTW, to prevent Ada from allocating anything for a type, you can use Storage_Size => 0. That would be my reecommendation in this case. *************************************************************** From: Victor Porton Sent: Saturday, October 21, 2017 12:32 PM Suppose we have a type T with Convention=>C. Let A be an access to T. I want "new T'(...)" with expected type A to call C function such as raptor_alloc_memory() to allocate the appropriate amount of memory and do initialization of T with this memory. (And similarly with freeing allocated memory, which I omit for brevity.) The address of the allocated memory should be the same (e.g. to facilitate memory deallocation both through Ada and through C) as the address of the C pointer returned by raptor_alloc_memory(). Allocating and freeing through Ada and through C should be able to be arbitrarily intermixed. Because it is a Convention=>C type, there should be no problem of interacting with C in this case, dot. You accused me of creating thin C library bindings. It is false: I do thick bindings. In the code I am writing, the below "new" operator is to be used in implementation not in interface, so there is nothing wrong with it. I would define an Ada memory pool for this. The only problem is that there is no prohibition for the Ada allocator to request greater alignment than T'Alignment. In this case the allocator would not be guaranteed to work correctly and this would lead to undefined behavior. It is possible to do this without "new" operator, like this (extracted from https://github.com/vporton/redland-bindings/blob/ada2012/ada/src/rdf-raptor-log.adb): Size: constant size_t := size_t((T'Max_Size_In_Storage_Elements * Storage_Unit + (char'Size-1)) / char'Size); Result2: constant chars_ptr := raptor_alloc_memory(Size); Result: constant A := Locator_Handle(Locator_Conv.To_Access(Result2)); Then I do assignment to "Result.all" to initialize the allocated memory. (here Locator_Conv converts between chars_ptr and Ada access, as defined in https://github.com/vporton/redland-bindings/blob/ada2012/ada/src/rdf-auxiliary-convert_void.adb) The above way should work (and I assume it's portable), but this is much less convenient than just calling "new T'(...)" would be. That it cannot be done through "new" is clearly a misfeature of Ada. It is a misfeature, dot. I propose to add the following construct: Add 'Max_Alignment pool attribute and the following syntax for a pool Pool: for Pool'Max_Alignment use ; Then allocator for a type T with T'Alignment would ensure (and otherwise raise Program_Error) that "T'Storage_Pool'Max_Alignment mod T'Alignment = 0" and request alignment which is a divisor of Max_Alignment(T'Storage_Pool). This can be used as for Pool'Max_Alignment use T'Alignment; Because T is the Convention=>C type we need to deal in the pool, this would suffice for it to work right always. *************************************************************** From: Randy Brukardt Sent: Tuesday, October 24, 2017 6:48 PM Sorry about the delay getting these messages posted. I've been fighting a high fever and not working much. Anyway, when you post from an alternative address like this one, you are certain to get the message delayed (all non-member mail requires moderation). OTOH, your normal address is getting quarantined by our spam filter for some reason that I need to investigate when I feel better; it may be possible to eliminate that block (it might be quite old). Anyway, on to the show... ... > Suppose we have a type T with Convention=>C. > > Let A be an access to T. I want "new T'(...)" with expected type A to > call C function such as raptor_alloc_memory() to allocate the > appropriate amount of memory and do initialization of T with this > memory. (And similarly with freeing allocated memory, which I omit for > brevity.) > > The address of the allocated memory should be the same (e.g. > to facilitate memory deallocation both through Ada and through C) as > the address of the C pointer returned by raptor_alloc_memory(). > Allocating and freeing through Ada and through C should be able to be > arbitrarily intermixed. This cannot work. As Dmitry noted, if one initializes objects on the Ada side then finalization data may be set up; if you *don't* finalize the objects on the Ada side, you'll leave that data pointing into the void and that will not work. All Ada types nominally require finalization; not all actually require any execution when finalized but you cannot easily tell what a particular implementation will do. > Because it is a Convention=>C type, there should be no problem of > interacting with C in this case, dot. I see your point, but this was never intended to be a use-case. > You accused me of creating thin C library bindings. It is > false: I do thick bindings. They look like thin C bindings to me, because the fact that the underlying library is in C is painfully obvious. Moreover, you're preventing Ada users from using anything without C-interfacing in extensions of your library (as the extensions have to be C-interfacing: no containers, streams, files, tasks, etc. [note the explicit use of access types would work, but I consider that evil - not shared by all]. > The only problem is that there is no prohibition for the Ada allocator > to request greater alignment than T'Alignment. In this case the > allocator would not be guaranteed to work correctly and this would > lead to undefined behavior. This is certainly not the only problem; you have usability issues at best and quite possibly serious potential of erroneous execution because of the finalization issue. My advice would be to write a true thick binding where almost nothing of the C interface shows through. Preferably with no visible access types at all (that provides the maximum flexibility for storage management on the Ada user - access types have to be manually managed). [Again, I note that other people may have a different definition of a thick binding.] *************************************************************** From: Victor Porton Sent: Wednesday, October 25, 2017 12:51 AM > This cannot work. As Dmitry noted, if one initializes objects on the > Ada side then finalization data may be set up; if you *don't* finalize > the objects on the Ada side, you'll leave that data pointing into the > void and that will not work. All Ada types nominally require > finalization; not Why not use Unchecked_Deallocation to free an object? I don't see any trouble here. Because it is a Convention=>C object, accordingly C rules (we are here to use both Ada and C rules simultaneously), finalization is always empty. > actually require any execution when finalized but you cannot easily > tell what a particular implementation will do. > > > Because it is a Convention=>C type, there should be no problem of > > interacting with C in this case, dot. > > I see your point, but this was never intended to be a use-case. OK, we can make a new use case. > > You accused me of creating thin C library bindings. It is > > false: I do thick bindings. > > They look like thin C bindings to me, because the fact that the > underlying library is in C is painfully obvious. Moreover, you're > preventing Ada users from using anything without C-interfacing in > extensions of your library (as the extensions have to be > C-interfacing: no containers, streams, files, tasks, etc. [note the > explicit use of access types would work, but I consider that evil - > not shared by all]. > > > The only problem is that there is no prohibition for the Ada > > allocator to request greater alignment than T'Alignment. In this > > case the allocator would not be guaranteed to work correctly and > > this would lead to undefined behavior. > > This is certainly not the only problem; you have usability issues at > best and quite possibly serious potential of erroneous execution > because of the finalization issue. I do not see finalization issue. Finalization is guaranteed to be no-op by C standard. As we interface with C and use Convention=>C, C standard applies! ***************************************************************