• On absurdity of collections 7.6.1 (11.1/3)

    From Dmitry A. Kazakov@21:1/5 to All on Wed Sep 29 11:09:58 2021
    For Ada programmers who wonder what it is, here is a practical example.
    Given the package specification:

    package P is
    type Item (Length : Positive) is
    new Ada.Finalization.Limited_Controlled with
    record
    Text : String (1..Length);
    end record;
    overriding procedure Finalize (X : in out Item);

    type Item_Ptr is access all Item;
    function New_Item
    ( Pool : in out Root_Storage_Pool'Class;
    Text : String
    ) return Item_Ptr;
    procedure Free is new Ada.Unchecked_Deallocation (Item, Item_Ptr);
    end P;

    and the program using it like this:

    Ptr : Item_Ptr := New_Item (Some_Pool, "A");

    ...

    Free (Ptr);

    Write the package body.

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Simon Wright@21:1/5 to Dmitry A. Kazakov on Wed Sep 29 12:05:22 2021
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> writes:

    type Item_Ptr is access all Item;
    function New_Item
    ( Pool : in out Root_Storage_Pool'Class;
    Text : String
    ) return Item_Ptr;

    What I don't see is how you can implement this, never mind any other
    problems.

    13.11(17) says

    "If Storage_Pool is not specified for a type defined by an
    access_to_object_definition, then the implementation chooses a
    standard storage pool for it in an implementation-defined manner."

    I see that 3.10(8) says

    "Access-to-object types are further subdivided into pool-specific
    access types [...] and general access types, whose values can
    designate the elements of any storage pool [...]"

    which implies that there *is* a mechanism.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to Simon Wright on Wed Sep 29 13:20:48 2021
    On 2021-09-29 13:05, Simon Wright wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> writes:

    type Item_Ptr is access all Item;
    function New_Item
    ( Pool : in out Root_Storage_Pool'Class;
    Text : String
    ) return Item_Ptr;

    What I don't see is how you can implement this, never mind any other problems.

    A naive, but wrong due to 7.6.1 (11.1/3) nonsense, implementation would be:

    function New_Item
    ( Pool : in out Root_Storage_Pool'Class;
    Text : String
    ) return Item_Ptr is
    type Ptr is access Item;
    for Ptr'Storage_Pool use Pool;
    Object : Ptr := new Item (Text'Length);
    begin
    Object.Text := Text;
    return Object.all'Unchecked_Access;
    end New_Item;

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Simon Wright@21:1/5 to Dmitry A. Kazakov on Wed Sep 29 22:38:42 2021
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> writes:

    On 2021-09-29 13:05, Simon Wright wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> writes:

    type Item_Ptr is access all Item;
    function New_Item
    ( Pool : in out Root_Storage_Pool'Class;
    Text : String
    ) return Item_Ptr;
    What I don't see is how you can implement this, never mind any other
    problems.

    A naive, but wrong due to 7.6.1 (11.1/3) nonsense, implementation would be:

    function New_Item
    ( Pool : in out Root_Storage_Pool'Class;
    Text : String
    ) return Item_Ptr is
    type Ptr is access Item;
    for Ptr'Storage_Pool use Pool;
    Object : Ptr := new Item (Text'Length);
    begin
    Object.Text := Text;
    return Object.all'Unchecked_Access;
    end New_Item;

    OK, that code compiles.

    What you'd need to happen when the returned Item_Ptr is freed would be
    for the mechanism of the actual pool to be invoked. But Item_Ptr was
    declared without any pool specified, so uses the default, and when the
    returned Item_Ptr is freed it uses the default pool's mechanism.

    But of course what actually happens with this code is that the returned Item_Ptr is left dangling; my test

    with P;
    with System.Pool_Local; -- GNAT special
    with Ada.Text_IO; use Ada.Text_IO;
    procedure Test is
    use P;
    Pool : System.Pool_Local.Unbounded_Reclaim_Pool;
    Ptr : Item_Ptr := New_Item (Pool, "hello");
    begin
    Put_Line (Ptr.Text);
    Free (Ptr);
    end Test;

    manages flukily to print "hello" before crashing at the Free (Ptr).

    I don't see how what you want can be achieved without every access type containing a reference to the pool the object was allocated from.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Randy Brukardt@21:1/5 to Dmitry A. Kazakov on Wed Sep 29 19:23:28 2021
    It's surely wrong, because you have to deallocate from the same pool as you allocated. And you have no way to save such a thing. There is no capability
    in Ada to use multiple pools with one access type.

    At best, you could directly call Allocate and Deallocate for this purpose,
    but it is impossible to use anything built-in.

    You could use the subpool mechanism (assuming you want a limited number of kinds of pools) to do this (each subpool could use a different allocation mechanism). But that would mean a giant pool type containing all of the implementations -- which seems suboptimal. Note that the subpool mechanism ensures that finalization happens when a subpool is destroyed.

    Randy.

    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message news:sj1i6g$7hv$1@gioia.aioe.org...
    On 2021-09-29 13:05, Simon Wright wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> writes:

    type Item_Ptr is access all Item;
    function New_Item
    ( Pool : in out Root_Storage_Pool'Class;
    Text : String
    ) return Item_Ptr;

    What I don't see is how you can implement this, never mind any other
    problems.

    A naive, but wrong due to 7.6.1 (11.1/3) nonsense, implementation would
    be:

    function New_Item
    ( Pool : in out Root_Storage_Pool'Class;
    Text : String
    ) return Item_Ptr is
    type Ptr is access Item;
    for Ptr'Storage_Pool use Pool;
    Object : Ptr := new Item (Text'Length);
    begin
    Object.Text := Text;
    return Object.all'Unchecked_Access;
    end New_Item;

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to Simon Wright on Thu Sep 30 10:07:17 2021
    On 2021-09-29 23:38, Simon Wright wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> writes:

    On 2021-09-29 13:05, Simon Wright wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> writes:

    type Item_Ptr is access all Item;
    function New_Item
    ( Pool : in out Root_Storage_Pool'Class;
    Text : String
    ) return Item_Ptr;
    What I don't see is how you can implement this, never mind any other
    problems.

    A naive, but wrong due to 7.6.1 (11.1/3) nonsense, implementation would be: >>
    function New_Item
    ( Pool : in out Root_Storage_Pool'Class;
    Text : String
    ) return Item_Ptr is
    type Ptr is access Item;
    for Ptr'Storage_Pool use Pool;
    Object : Ptr := new Item (Text'Length);
    begin
    Object.Text := Text;
    return Object.all'Unchecked_Access;
    end New_Item;

    OK, that code compiles.

    What you'd need to happen when the returned Item_Ptr is freed would be
    for the mechanism of the actual pool to be invoked. But Item_Ptr was
    declared without any pool specified, so uses the default, and when the returned Item_Ptr is freed it uses the default pool's mechanism.

    That would be another language bug, if true, because 13.11.2 is silent
    about that. But the first bug is that New_Item is not implementable,
    well it actually is, but in a very clumsy way (see my answer to Randy).

    But of course what actually happens with this code is that the returned Item_Ptr is left dangling; my test

    with P;
    with System.Pool_Local; -- GNAT special
    with Ada.Text_IO; use Ada.Text_IO;
    procedure Test is
    use P;
    Pool : System.Pool_Local.Unbounded_Reclaim_Pool;
    Ptr : Item_Ptr := New_Item (Pool, "hello");
    begin
    Put_Line (Ptr.Text);
    Free (Ptr);
    end Test;

    manages flukily to print "hello" before crashing at the Free (Ptr).

    It should print it twice, because Finalize must be called twice. Once
    inside New_Item, then in Free.

    I don't see how what you want can be achieved without every access type containing a reference to the pool the object was allocated from.

    Yes, every general access type that permits instantiation of Unchecked_Dellocation must indicate the target object's pool, directly,
    e.g. per fat pointer, or indirectly by some other schema. I see nothing
    in RM that allows a different implementation. But it is could be a bug
    by omission and I am not a language lawyer anyway.

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to Randy Brukardt on Thu Sep 30 10:06:21 2021
    On 2021-09-30 02:23, Randy Brukardt wrote:
    It's surely wrong, because you have to deallocate from the same pool as you allocated. And you have no way to save such a thing. There is no capability in Ada to use multiple pools with one access type.

    You mean Unchecked_Dellocation? Maybe, then it is another language bug. However, the object collection schema is irrelevant for a while. The
    first problem is that Finalize is called twice.

    At best, you could directly call Allocate and Deallocate for this purpose, but it is impossible to use anything built-in.

    Actually it is possible using a fake pool:

    type Fake_Pool is new Root_Storage_Pool with record
    Pool : access Root_Storage_Pool'Class;
    end record;

    Allocate goes like this:

    procedure Allocate
    ( Pool : in out Fake_Pool;
    Storage_Address : out System.Address;
    Size : Storage_Count;
    Alignment : Storage_Count
    ) is
    begin
    Pool.Pool.Allocate (Storage_Address, Size, Alignment);
    end Allocate;

    Then:

    Fake : Fake_Pool;
    type Ptr is access Item;
    for Ptr'Storage_Pool use Fake; -- "collection" lives here

    function New_Item
    ( Pool : in out Root_Storage_Pool'Class;
    Text : String
    ) return Item_Ptr is
    Object : Ptr;
    begin
    Fake.Pool := Pool'Unchecked_Access; -- Add mutex here
    Object := new Item (Text'Length);
    Object.Text := Text;
    return Object.all'Unchecked_Access;
    end New_Item;

    Now the object is intact.

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to Simon Wright on Thu Sep 30 10:49:11 2021
    On 2021-09-30 10:35, Simon Wright wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> writes:

    It should print it twice, because Finalize must be called twice. Once
    inside New_Item, then in Free.

    That's in your implementation of Finalize, which you haven't shown us

    procedure Finalize (X : in out Item) is
    begin
    Put_Line (X.Text & " is dead");
    end Finalize;

    P.S. You can use the standard pool:

    Ptr : Item_Ptr;
    type String_Ptr is access String;
    begin
    Ptr := New_Item (String_Ptr'Storage_Pool, "A");
    Free (Ptr);

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Simon Wright@21:1/5 to Dmitry A. Kazakov on Thu Sep 30 09:35:00 2021
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> writes:

    It should print it twice, because Finalize must be called twice. Once
    inside New_Item, then in Free.

    That's in your implementation of Finalize, which you haven't shown us

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to G.B. on Thu Sep 30 20:52:55 2021
    On 2021-09-30 20:23, G.B. wrote:
    On 29.09.21 11:09, Dmitry A. Kazakov wrote:
    For Ada programmers who wonder what it is,
    What's the reasoning behind run-time selection of storage pools?

    It happens quite frequently. Here is an example without controlled
    objects, just an illustration of a dynamically selected storage pool.

    Consider a JSON parser. It is be an Ada object with a buffer inside
    which size is a discriminant. On top of the buffer sits an arena pool.
    The parts of the parsed JSON object are allocated in the arena. After
    parsing the result can be used until the next parsing that will sweep
    the arena, no Unchecked_Deallocate.

    In this case the collection rule will have no effect since JSON objects
    do not require controlled components (or tasks, yet another thing killed
    by the collection).

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From G.B.@21:1/5 to Dmitry A. Kazakov on Thu Sep 30 20:23:34 2021
    On 29.09.21 11:09, Dmitry A. Kazakov wrote:
    For Ada programmers who wonder what it is,
    What's the reasoning behind run-time selection of storage pools?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Randy Brukardt@21:1/5 to Dmitry A. Kazakov on Thu Sep 30 20:40:33 2021
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message news:sj5127$1bbu$1@gioia.aioe.org...
    On 2021-09-30 20:23, G.B. wrote:
    On 29.09.21 11:09, Dmitry A. Kazakov wrote:
    For Ada programmers who wonder what it is,
    What's the reasoning behind run-time selection of storage pools?

    It happens quite frequently. Here is an example without controlled
    objects, just an illustration of a dynamically selected storage pool.

    Consider a JSON parser. It is be an Ada object with a buffer inside which size is a discriminant. On top of the buffer sits an arena pool. The parts
    of the parsed JSON object are allocated in the arena. After parsing the result can be used until the next parsing that will sweep the arena, no Unchecked_Deallocate.

    In this case the collection rule will have no effect since JSON objects do not require controlled components (or tasks, yet another thing killed by
    the collection).

    To implement an arena pool, you need to use the subpool mechanism (which
    does properly handle finalization when you "sweep the pool" as you put it). Each "arena" is a separate subpool, and you can dump the entire subpool with Unchecked_Deallocate_Subpool.

    It helps to use the tool designed for the job. ;-)

    Randy.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Randy Brukardt@21:1/5 to Dmitry A. Kazakov on Thu Sep 30 20:37:52 2021
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message news:sj3r7l$pla$2@gioia.aioe.org...
    ...
    Yes, every general access type that permits instantiation of Unchecked_Dellocation must indicate the target object's pool, directly,
    e.g. per fat pointer, or indirectly by some other schema. I see nothing in
    RM that allows a different implementation. But it is could be a bug by omission and I am not a language lawyer anyway.

    Each access type has only one pool, and all allocations and deallocations
    for that access type use that pool. You can see at RM 13.11(13) that you can retrieve the storage pool of T, that wouldn't be possible if it could have multiple pools. Allocation for T uses T'Storage_Pool (RM 13.11(16)), and deallocation for T also uses T'Storage_Pool (13.11.2(9/3) - I see this
    wording isn't as clear as it could be, but "the storage_pool" here is the
    one for the type T. It is clear, however, from 13.11.2(16/3) that it is erroneous to deallocate an object for some other pool than the pool of the access type passed to the instance of Unchecked_Deallocation.

    Surely no one would expect the pool to be associated with the object -- it would be impossible to interface to C or most other languages that way. Note that GNAT insists that all access types are C-compatible.

    As I've said repeatedly, if you want the behavior of multiple pools with a single access type, you have to use the subpool mechanism somehow.

    Randy.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to Randy Brukardt on Fri Oct 1 09:57:35 2021
    On 2021-10-01 03:37, Randy Brukardt wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message news:sj3r7l$pla$2@gioia.aioe.org...
    ...
    Yes, every general access type that permits instantiation of
    Unchecked_Dellocation must indicate the target object's pool, directly,
    e.g. per fat pointer, or indirectly by some other schema. I see nothing in >> RM that allows a different implementation. But it is could be a bug by
    omission and I am not a language lawyer anyway.

    Each access type has only one pool, and all allocations and deallocations
    for that access type use that pool. You can see at RM 13.11(13) that you can retrieve the storage pool of T, that wouldn't be possible if it could have multiple pools. Allocation for T uses T'Storage_Pool (RM 13.11(16)), and deallocation for T also uses T'Storage_Pool (13.11.2(9/3) - I see this wording isn't as clear as it could be, but "the storage_pool" here is the
    one for the type T. It is clear, however, from 13.11.2(16/3) that it is erroneous to deallocate an object for some other pool than the pool of the access type passed to the instance of Unchecked_Deallocation.

    At least instantiations of Unchecked_Deallocation should be illegal with general access.

    Surely no one would expect the pool to be associated with the object -- it would be impossible to interface to C or most other languages that way. Note that GNAT insists that all access types are C-compatible.

    Normally you cannot allocate C objects intended to be freed from C using
    Ada allocator. This is a common mistake of binding designers which
    promptly crashes the program.

    But I see no problem here, especially because the access type must have
    C convention anyway.

    As I've said repeatedly, if you want the behavior of multiple pools with a single access type, you have to use the subpool mechanism somehow.

    Honestly I still try to find a useful case for subpools. It is nothing
    that could not be easily implemented using existing pools or might help fighting off collections etc.

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)