AdaCore / ada-spark-rfcs

Platform to submit RFCs for the Ada & SPARK languages
62 stars 28 forks source link

Storage Model (simplified) #76

Closed QuentinOchem closed 1 year ago

QuentinOchem commented 3 years ago

This is a simplified version of the previous storage model proposal (https://github.com/AdaCore/ada-spark-rfcs/pull/67).

Full text is here

dismukes commented 3 years ago

Parameter passing creates some difficulties. For parameters of types that would normally be passed by reference (such as many composite types), when passing an object belonging to a Storage_Model type (e.g., A.all), the actual will need to be copied, because the callee will access it indirectly using host memory addressing, potentially having a large performance cost. Alternatively, such actuals could be disallowed. For by-reference types, which require passing by reference (such as limited types), the only choice seems to be make such actual parameters illegal.

dismukes commented 3 years ago

Similar to by-reference parameter passing, other aliasing can be problematic. Applying 'Access to an aliased object that resides in host memory when the type of the attribute is a Storage_Model access type should be disallowed (also conversions from access values that come from a Storage_Model that doesn't match the target access type). Renamings can probably be handled OK, but do add some implementation complexity.

dismukes commented 3 years ago

Build-in-place calls where the function is used to initialize a result object associated with a Storage_Model access type (basically an allocator for such a type) should maybe be disallowed, as they will create additional complexity for the BIP implementation, which is already complex and bug-prone.

dismukes commented 3 years ago

I have a general concern about implementation complexity/cost for supporting addressing of components in Storage_Model objects (selecting and indexed components, slicing, aggregate initialization). There might also be impacts on gigi, or cases where the GNAT front end has insufficient knowledge of representation for component addressing. More detailed study is needed to assess how big an issue this is.

QuentinOchem commented 3 years ago

Parameter passing creates some difficulties. For parameters of types that would normally be passed by reference (such as many composite types), when passing an object belonging to a Storage_Model type (e.g., A.all), the actual will need to be copied, because the callee will access it indirectly using host memory addressing, potentially having a large performance cost. Alternatively, such actuals could be disallowed. For by-reference types, which require passing by reference (such as limited types), the only choice seems to be make such actual parameters illegal.

This is indeed something we lost when moving from subtypes to only access types - in the initial proposal you could mark a subtype to be of a given Storage_Model and thus you would keep this information along.

I agree with your assessment and would indeed make any parameter passing that is allowed to be by reference illegal at this stage. We have several way to make progress on this as a second step, two come to mind:

Similar to by-reference parameter passing, other aliasing can be problematic. Applying 'Access to an aliased object that resides in host memory when the type of the attribute is a Storage_Model access type should be disallowed (also conversions from access values that come from a Storage_Model that doesn't match the target access type). Renamings can probably be handled OK, but do add some implementation complexity.

Agreed as well - we should mark these as illegal for now.

Build-in-place calls where the function is used to initialize a result object associated with a Storage_Model access type (basically an allocator for such a type) should maybe be disallowed, as they will create additional complexity for the BIP implementation, which is already complex and bug-prone.

Do you have an example of the above? I don't understand what case this refers to.

dismukes commented 3 years ago

For build-in-place calls, I'm basically talking about an allocator for an access-to-Storage_Model_Type that's initialized by a BIP function call ("new STM'( )"). In the GNAT implementation, the called BIP function performs the allocation, and needs to know where to allocate the object (such as in a Storage_Pool passed in to the function).

QuentinOchem commented 3 years ago

For build-in-place calls, I'm basically talking about an allocator for an access-to-Storage_Model_Type that's initialized by a BIP function call ("new STM'( )"). In the GNAT implementation, the called BIP function performs the allocation, and needs to know where to allocate the object (such as in a Storage_Pool passed in to the function).

Understood. That one might actually be important in the long run, in particular as it allows to constraint the target object using the value returned by an expression. So - to the contrary of the previous two - I would not mark that as a feature limitation, but perhaps rather as an implementation limitation. So make things clear, that would mean that instead of writing:

X := new Y'(others => <>);

You would write:

X := new Y; -- and maybe some constraint X.all := (others => <>);

dismukes commented 3 years ago

Understood. That one might actually be important in the long run, in particular as it allows to constraint the target object using the value returned by an expression. So - to the contrary of the previous two - I would not mark that as a feature limitation, but perhaps rather as an implementation limitation. So make things clear, that would mean that instead of writing:

X := new Y'(others => <>);

You would write:

X := new Y; -- and maybe some constraint X.all := (others => <>);

I was referring to the function call case rather than aggregates. That sort of workaround doesn't apply for BIP function calls, which are currently only supported for limited types. In any case, it could certainly be treated as an implementation limitation.

QuentinOchem commented 3 years ago

I was referring to the function call case rather than aggregates. That sort of workaround doesn't apply for BIP function calls, which are currently only supported for limited types. In any case, it could certainly be treated as an implementation limitation.

So for the sake of completness, we're talking:

X := new Y'(Some_Function (Z));

Which would has to be written:

X := new Y; -- and maybe some constraint X.all := Some_Function(Z);

This sounds like a very reasonable implementation limitation at this stage indeed.

dismukes commented 3 years ago

I was referring to the function call case rather than aggregates. That sort of workaround doesn't apply for BIP function calls, which are currently only supported for limited types. In any case, it could certainly be treated as an implementation limitation.

So for the sake of completness, we're talking:

X := new Y'(Some_Function (Z));

Which would has to be written:

X := new Y; -- and maybe some constraint X.all := Some_Function(Z);

This sounds like a very reasonable implementation limitation at this stage indeed.

Right, except you can't use that workaround in the limited (build-in-place) function-call case, since you can't assign.

QuentinOchem commented 3 years ago

Right, except you can't use that workaround in the limited (build-in-place) function-call case, since you can't assign.

Right - so my workaround isn't applicable for limited types - thanks for pointing it out (a second time - I misunderstood the first;-)). Still a very reasonable limitation for an initial implementation.