Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

Passing data type and its attribute with different intents

Mateusz_G_
Beginner
995 Views

Lets say I have a data type:

  type my_t
    real(8), dimension(3) :: x       
    real(8), dimension(3) :: y      
    real(8)               :: area    
  end type my_t

 

can I call a function in the following way:

call foo(my, my%x)

 

where subroutine is defined as follows

subroutine foo( my, my_x)
  implicit none
  ! --- Argument list --- 
  type(m_t), intent(in)    :: my 
  real(8),   intent(inout) :: my_x
  .
  .
  .
end subroutine foo

 

So the subroutine has my type as intent(in) but my_x (one of its instances) as intent(inout), but the my_x is a part of type my_t. Is it allowed to pass the datatype as intent(in) and one of its attributes as intent(inout)

Is this kind of call allowed / correct / good practice ?

 

0 Kudos
19 Replies
FlyingHermes
New Contributor I
995 Views
The correct way of doing it would be to defined the "foo" procedure as:
subroutine foo( my, my_x)
  implicit none
  ! --- Argument list --- 
  type(triangle_t), intent(in)    :: my 
  real(8),          intent(inout) :: my_x
  ...
end subroutine

Note that the dummy argument cannot be a component of a derived-type, but the actual argument can.

Also, the it is legal to call a procedure with:

  • one "intent(in)" argument associated to a devied-type and
  • one "intent(inout)" argument associated to a component of this devied-type.

this is perfectly correct.

Whether or not it is a good practice, depends on the context of using such an approach.

If all the components of the derived-type (or lots of them) are actually needed as inputs inside the procedure, but only one component needs to be modified, your approach represent a good solution.

0 Kudos
Mateusz_G_
Beginner
995 Views

Oh sorry for the typos in my post. I edited and corrected it. 

Thanks for the answer, although I am not sure if I was understood correctly, due to the typos.

My question is referring to the aliasing rules.

Mateusz

0 Kudos
IanH
Honored Contributor II
995 Views

Careful!  I think what is being described as "perfectly correct" isn't.

If the actual argument associated with the `my_x` dummy argument is a subobject (e.g. a component) of the actual argument associated with the `my` dummy argument, and if the intent(inout) argument is defined in the procedure (which you would normally expect to happen for an intent(inout) argument), then the code in #2 is non-conforming - it violates Fortran's argument aliasing rules (F2008 12.5.2.13p1 (3)) and similarly the rules around INTENT (5.3.10p2).

0 Kudos
Mateusz_G_
Beginner
995 Views

Thanks IanH. From what my colleague at Univeristy told me, your answer seem to confirm it.

Assuming it is not allowed, as you said, is there any nice and clean way of doing a call like that without creating a long arguments list. I am talking about a case where my_t is huge type that contains many subobjects and many of them are used within a subroutine as intent(in), to get only one output e.g. my%x.

 

 

0 Kudos
IanH
Honored Contributor II
995 Views

Three options come to mind. 

- add the target attribute to both dummy arguments and mark the `my` dummy argument also as intent(inout).  TARGET (and POINTER) relax the aliasing restrictions.

- hold the component via a pointer component.  Pointer subobjects are only considered subobjects of the containing object in contexts to do with pointer association.

- change the interface of the procedure by deleting the `my_x` dummy argument and replacing it with arguments that describe how to navigate to the sub-object of interest.  Once you are in the procedure you can use the associate construct to give yourself a "legal" alias for the subobject.

(I just dealt with something very similar to this an hour or so ago in my own code.  In my case the subobject being passed was an element of an array component.  The component was fixed, so I merely replaced passing the element with the index of the element.)

Depending on what is required out of the `my` derived type object, you may be able to just pass other subobjects of it, as opposed to the entire object.

0 Kudos
Mateusz_G_
Beginner
995 Views

Thanks.

About option one, isn`t it the same then as just passing whole 'my' type as intent(inout)? 

I am not sure if I understand options 2 and 3 though.

What I want to achieve is: to have a clean subroutine interface where it is defined explicitly what is intent in what is inout and in the same time avoid long subroutine arguments list ;).

 

0 Kudos
IanH
Honored Contributor II
995 Views

When all dummy arguments that might be associated with overlapping parts of some object have the target attribute the compiler will generate code that defends against that potential overlap, so it is not the same.

Perhaps the following elaboration might help.

!-------------------------------------------------------------------------------
! use TARGET to inform compiler that `my` and `my_x` may be aliased.

type my_t
    real :: x(3)
    ...
end type my_t

subroutine option_one(my, my_x)
  type(my_t), intent(inout), target :: my 
  real, intent(inout), target :: my_x
  
  my_x = ...
end subroutine option_one

type(my_t) :: arg
call option_one(arg, arg%x(2))


!-------------------------------------------------------------------------------
! Hold component via pointer (it is then not a subobject of the derived type
! in a value context).

type my_t2
  real, pointer :: x(:) => null()
  ...
contains
  final :: my_t2_final
end type my_t2

subroutine option_two(my, my_x)
  implicit none
  ! --- Argument list --- 
  type(my_t), intent(inout) :: my 
  real, intent(inout) :: my_x
  
  my_x = ...
end subroutine option_two

subroutine my_t2_final(scalar)
  type(my_t2), intent(inout) :: scalar
  if (associated(scalar%x)) deallocate(scalar%x)
end subroutine my_t2_final

type(my_t) :: arg
allocate(arg%x(3))
call option_two(arg, arg%x(2))


!-------------------------------------------------------------------------------
! Dummy argument (`i` here) describing which subobject is of interest.

subroutine option_three(my, i)
  type(my_t), intent(inout) :: my 
  integer, intent(in) :: i
  associate(my_x => my%x(i))
    my_x = ...
  end associate
end subroutine option_three

type(my_t) :: arg
call option_three(arg, 2)

 

0 Kudos
Mateusz_G_
Beginner
995 Views

Thank You very much for the detailed comment and your time.

However, I don`t want to pass whole data type 'my' as intent(inout) but as intent(in) with one exception for subobject x i.e. my%x. That is my goal.

In other words: I don`t want to let anyone change any other attribute of data type 'my' within this subroutine except for variable my%x.

 Sorry if I am not expressing my self precise enough.

 

0 Kudos
jimdempseyatthecove
Honored Contributor III
995 Views

It is perfectly acceptable to write non-conformant code with respect aliasing... provided you account for and correct for potential aliasing in the code. Depending on your procedure, it may be as simple of a solution as to not write any of the intent(out) and/or intent(inout) and/or no intent, until after you've collected (read) all the pertinent intent(in) and/or intent(in/out). This may or may not require some holding variables. Example with caveat:

module foo
subroutine foo( my, my_x_ret)
  implicit none
  ! --- Argument list --- 
  type(m_t), intent(in)    :: my 
  real(8),   intent(inout) :: my_x_ret
  ! locals
  real(8), target          :: my_x ! or replace target with volatile
  ! copy potentially aliased variables to local
  my_x = my_x_ret
  .
  .
  ! finally, copy locals to return variables
  my_x_ret = my_x
end subroutine foo

The use of TARGET is intended to force the compiler optimization from optimize out of the code the use of the temporary variable. A different choice, and IMHO better choice is to remove the target/volatile then use

!$OMP ATOMIC
my_x_ret = my_x
!$OMP END ATOMIC

FWIW I do not "understand" why Fortran itself does not have something analogous (ATOMIC_REF not valid in this case).

Jim Dempsey

0 Kudos
Mateusz_G_
Beginner
995 Views

Do I understand correctly then, that what I want to do is correct as long as the local variable is used in order to explicitly present to the compiler the fact that one of the instances of data type 'my' is intent(inout)? When writing explicitly I mean locally inside a subroutine.

For me as for a person with more engineering background to the example presented is same as what my code should do - ideally. Ideally meaning, if the compiler sees that one of the arguments of subroutine is (inout) but it is a part of bigger data type which is passed as (intent(in)) then it should switch intent for this one variable to inout. But of course I know it is a very naive thinking and I guess I need more deep understanding of how 'the stuff' works inside to see the reason why the code presented by jimdempsey is valid, while mine not.

If You could also comment on this, i.e:   why is this local target needed, or in other words: where this requirement has its origin - is the reason somewhere on the compiler side, logical/consistency side, hardware side or maybe on the definition of pointer and how it is stored in memory etc... or other? 

thanks!

 

0 Kudos
Mateusz_G_
Beginner
995 Views

Ahh, I am sorry, I just noticed that you explained that the reason for the local target, i.e. it is to prevent compiler optimisation. So do I understand correctly that it is a compiler optimisation which give problems for case like that? 

So what is it in particular (which part of compiler optimisation) that causes it?

 

0 Kudos
IanH
Honored Contributor II
995 Views

jimdempseyatthecove wrote:

It is perfectly acceptable to write non-conformant code with respect aliasing... provided you account for and correct for potential aliasing in the code. Depending on your procedure, it may be as simple of a solution as to not write any of the intent(out) and/or intent(inout) and/or no intent, until after you've collected (read) all the pertinent intent(in) and/or intent(in/out). This may or may not require some holding variables. Example with caveat:

module foo
subroutine foo( my, my_x_ret)
  implicit none
  ! --- Argument list --- 
  type(m_t), intent(in)    :: my 
  real(8),   intent(inout) :: my_x_ret
  ! locals
  real(8), target          :: my_x ! or replace target with volatile
  ! copy potentially aliased variables to local
  my_x = my_x_ret
  .
  .
  ! finally, copy locals to return variables
  my_x_ret = my_x
end subroutine foo

The use of TARGET is intended to force the compiler optimization from optimize out of the code the use of the temporary variable. A different choice, and IMHO better choice is to remove the target/volatile then use

!$OMP ATOMIC
my_x_ret = my_x
!$OMP END ATOMIC

FWIW I do not "understand" why Fortran itself does not have something analogous (ATOMIC_REF not valid in this case).

Jim Dempsey

Discussion around atomic is a red herring, the basic issue has nothing to do with concurrency.  In the general case, the potential for aliasing may also affect the nature of the call and return from the subroutine - consider things like copies of arguments being passed. If `my_x_ret` is associated with a non-pointer component, then you need to have that last assignment occur in the calling scope after the subroutine has concluded - i.e. you pass a "manual" copy of the component into the subroutine.  When you structure your code that way you no longer have aliasing inside the subroutine.  (Similarly, if you say an object is INTENT(IN), then all of the subobjects of that object have INTENT(IN), which is a promise by the programmer to the compiler that the value of any and all of those objects will not change inside the procedure, regardless of the means by which that value might change, direct reference or some sort of indirect aliasing trickery.)

So if you want to use temporaries (which is an option I didn't list above), the code needs to be structured like:

type my_t
  real :: x(3)
  ...
end type my_t

type(my_t) :: arg
real :: tmp_x
...
tmp_x = arg%x(2)
call foo(arg, tmp_x)
arg%x(2) = tmp_x


subroutine foo(my, my_x)
  type(my_t), intent(in) :: my
  real, intent(inout) :: my_x
  my_x = ...
end subroutine foo

 

0 Kudos
jimdempseyatthecove
Honored Contributor III
995 Views

IanH,

The use of the ATOMIC or VOLATILE are not used for the purpose of atomicity nor volatility (nor concurrency), rather the purpose is a means to circumvent compiler optimizations from removing the expressed sequence of, and possibly the instructions themselves, that use the temporary. The Fortran language has no attribute for a type that effectively says "do not optimize out statements using this variable". It would be permitted to use registerized copies of the variable as long as it were not "equivilence'd" to the calling/actual argument or other such attributed local variables. VOLATILE should accomplish this, but at an added expense of write/read to/from RAM. An alternative would be to write an external subroutine, perhaps called COPY_DAMMIT(a,b) that is generic .AND. not available to the compiler for inlining.

While your workaround works, one must insert this into all calls of foo. IMHO the better choice of performing the fix is inside the subroutine... *** provided that the aliased use of self-modifying arg%x(2) was not intended by foo.

Mateusz,

There is no proper way to fix this because you will have two opposing sides as what is the proper way to handle this ambiguity. Each similar usage may require a different course of action (hence the non-conforming statement and/or behavior is undefined). It is the programmer's responsibility to avoid ambiguous situations and at least document how you handle it them with your code.

The compiler could issue a warning in this situation... provided it has the subroutine interface.

Jim Dempsey

 

 

0 Kudos
IanH
Honored Contributor II
995 Views

The only way you can "fix it" inside the subroutine (i.e. without making changes elsewhere) is by making both dummy arguments INTENT(INOUT) and TARGET, as per option one in #8.  Then the code is conforming and the observable behaviour required by the standard is well defined.

Otherwise, for example, the compiler can look at the promise made by the INTENT(IN) attribute on the dummy argument for the entire object of derived type, and make appropriate optimisations in the calling scope.  Consider the simplified example:

TYPE my_t
  REAL :: x
END TYPE my_t

SUBROUTINE foo(my, my_x)
  TYPE(my_t), INTENT(IN) :: my
  REAL, INTENT(INOUT) :: my_x
  my_x = 2.0
END SUBROUTINE foo

TYPE(my_t) :: arg
arg%x = 3.0
CALL foo(arg, arg%x)
PRINT *, arg%x

Because the `my` argument of foo is INTENT(IN), the language rules (which here are requirements on the programmer) mean that the compiler may assume that any aspect of the value of the actual argument associated with `my` will not be changed.  Because the `x` component is not a pointer component, the `x` component is a subobject of `arg` in all contexts and is therefore part of the value of `arg`, therefore the subroutine must not change the value of that `x` component of `arg`.  The compiler can see that the value 3.0 is assigned to `arg%x` prior to foo, therefore after the call the value of `arg%x` must still be 3.0, therefore, perhaps as part of its constant propagation analysis, the compiler could simply substitute the value 3.0 in for the output item in the print statement.

Even if you put the INTENT specification aside, without the TARGET or POINTER attribute on the dummy arguments, the compiler may also arrange the way that it invokes the subroutine in a way that assumes that there is no aliasing going on.  Perhaps this is unlikely to be a realistic implementation in practice given the circumstances (particularly the use of scalar arguments), but just for example the rules permit a compiler to create a temporary copy of the entire object of derived type, pass that to the procedure, and then when the procedure terminates copy the contents of that temporary back onto the original object.  This would obliterate any changes made to the component inside the procedure. 

Going further, a very smart compiler may see an object and a subobject both being associated as actual arguments to a procedure that hasn't got TARGET or POINTER on the dummy arguments, and then assume that either of those arguments will not be defined or redefined inside the procedure, even without knowing anything more about the internals of the procedure beyond its interface, because that's what the language rules require of the program.  It can assume the lack of definition or redefinition inside the procedure (runtime concept) even if in the interface the arguments are specified as INTENT(INOUT) (compile time concept)!

0 Kudos
jimdempseyatthecove
Honored Contributor III
995 Views

IanH,

That is a good analysis of the situation and pitfalls. One of the things you missed (but may have indirectly implied) is if foo calls another procedure passing these arguments (by reference) and not knowing if that procedure (or descendants) muck around in a way not intended. Because of these reasons, it would be effective to have an attribute (name to be haggled over) that can be applied to a local variable that specifies it is to be used. While you could place TARGET on the temporary variable, the compiler optimization might be (too) smart enough and optimize its use out of the code. Maybe Steve can comment on this when he gets back.

Another alternate way to force the compiler to use the local variable would be something like this:

module CantHappen_mod
logical :: CantHappen = .false.
end module CantHappen

localVariable = dummyVariable
if(CantHappen) CALL RoutineWithNoInterface(localVariable)

This is why I suggest a compile intrinsic (or attribute) that effectively forces the compiler to use the code as written.

Jim Dempsey

0 Kudos
Mateusz_G_
Beginner
995 Views

Thank you for very informative comments :).

Mateusz

0 Kudos
fedor_R_
Beginner
995 Views

From what I gather, it's best to avoid any kind of aliasing in Fortran. What would be a proper way of implementing the following?

Say, I have two derived types: base_t and child_t, each one defined in its own separate module. child_t extends base_t. base_t is an abstract type and has a deferred procedure, which it calls at some point. The child type must provide a definition for this procedure. The problem is that this procedure is supposed to change a private attribute of the base type which is not accessible to the child (add new coordinates to the coordinates array). I was going to pass it as an argument to this deferred procedure, but after reading this thread it seems like a bad idea:

! A simplified example of the interface block for the deferred procedure.
interface
    subroutine append_coordinates_func_t (this, x) 
        implicit none
        class(base_t), intent(in) :: this
        real, dimension(:,:), allocatable, intent(in out) :: x
    end subroutine append_coordinates_func_t
end interface

The call to the procedure would look something like call this%append_coordinates(this%x), where this%x is a private attribute of the base type. But it's aliasing and won't do. What would you suggest doing in this case? I'm especially interested in the "right" way to do it, not a way. Suggestions on changing the code structure (like how to deal with the whole inheritance of a type with a private component thing) are welcome too.

P.S. Thanks for any help in advance. I've originally learnt C++, and when it comes to things like OOP it is sometimes difficult to do things Fortran way rather than simply translate C++ principles into Fortran.

Fedor

0 Kudos
FortranFan
Honored Contributor II
995 Views

fedor R. wrote:

.. The problem is that this procedure is supposed to change a private attribute of the base type which is not accessible to the child (add new coordinates to the coordinates array)... The call to the procedure would look something like call this%append_coordinates(this%x), where this%x is a private attribute of the base type. But it's aliasing and won't do. What would you suggest doing in this case? I'm especially interested in the "right" way to do it, not a way. ..

Not sure I understand your issue completely; perhaps a small example would help.

You say you're trying to "add" something (new coordinates), so I assume your call to the procedure from the consumer side will be something like "call foo%append_coordinates( some_new_data )" where foo is an instance of child class.  Now I assume you have a valid reason for a deferred procedure for append_coordinates, perhaps the child processes the "some_new_data" also?  If so, why can't you add a setter procedure, append_coordinates_to_base to your abstract base type and from your append_coordinates procedure (the implementation of the deferred one) invoke the setter procedure in the base class: "call this%append_coordinates_to_base( some_new_data )" before doing its own processing of the new data?  How does "call this%append_coordinates(this%x)" come into the play?

But if the child doesn't need to do anything with the new coordinates data, why bother with the deferred procedure at all?  Just have a public setter procedure in the abstract base type and all children of said base type will inherit it and the consumer can invoke it directly as "call foo%set..(..)" where, as explained above, foo is an instance of the child class.

Note when you have your own setter procedure that work with private data of a type, doesn't it provide sufficient protection of the procedure only working on required data and then the passed object for the type can be INTENT(INOUT) - why do you need it to be INTENT(IN)?

P.S.> It may be better if your comment and subsequent discussions are in a separate thread so as not to confuse the issues with the original post of this thread; you can reference this thread to provide context. 

0 Kudos
fedor_R_
Beginner
995 Views

FortranFan wrote:

P.S.> It may be better if your comment and subsequent discussions are in a separate thread so as not to confuse the issues with the original post of this thread; you can reference this thread to provide context. 

Thanks for your response! Followed your advice and created a separate thread (https://software.intel.com/en-us/forums/intel-fortran-compiler-for-linux-and-mac-os-x/topic/606067) with a proper example. Responded to your comment there, although I think you're right, and I wasn't clear enough in my original post.

Fedor

0 Kudos
Reply