Skip to content

Explicit finalization for dual types#3

Draft
bhourahine wants to merge 1 commit intomainfrom
finalizer
Draft

Explicit finalization for dual types#3
bhourahine wants to merge 1 commit intomainfrom
finalizer

Conversation

@bhourahine
Copy link
Owner

Deallocates internal structure storage whenever dual objects go out of scope or are deallocated, including trapping for cases like intent(out) start up where the internals are not allocated on initial finalization.

@bhourahine bhourahine requested a review from fpenunuri July 23, 2025 16:29
@bhourahine bhourahine marked this pull request as draft July 23, 2025 16:30
Copy link
Collaborator

@fpenunuri fpenunuri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While using a finalizer (final :: dual_finalizer_${name}$${bits}$) for the
dual type upon leaving scope is a nice approach, it may cause conflicts
with some functions and subroutines that use the elemental attribute.

Perhaps the following approach would be more convenient:

...
public :: clear_dual

...

!---
interface clear_dual
#:for name, type, label in FLAVOURS
#:for bits in REALMODEL
module procedure clear_dual_${label}$${bits}$
#:endfor
#:endfor
end interface clear_dual
!--

...
contains

!---
#:for name, type, label in FLAVOURS
#:for bits in REALMODEL
subroutine clear_dual_${label}$${bits}$(x)
type(dual_${name}$${bits}$), intent(inout) :: x
if (allocated(x%f)) deallocate(x%f)
end subroutine clear_dual_${label}$${bits}$
#:endfor
#:endfor
!---

@aradi
Copy link
Collaborator

aradi commented Jul 25, 2025

Please note, that the finalizer as defined in the PR has no actual effect IMO. The f field in the derived type has the attribute allocatable, and would be, therefore, automatically deallocated by the compiler when the derived type instance goes out of scope anyway. So the current finalizer only does manually what would happen automatically at the same time anyway.

@bhourahine
Copy link
Owner Author

@aradi That suggests explicit finalization is not yet needed for this structure.
@fpenunuri If the structure becomes more complicated in the future, the finalizer could be made elemental impure (F2008), but of course this then means various other methods will need to be no longer pure.
I'll try out some tests with valgrind and various compilers before either a) closing this PR or b) changing to elemental compatibility.

@aradi
Copy link
Collaborator

aradi commented Jul 29, 2025

@bhourahine Indeed, the derived type in its current form does not require any finalization. Also, as you mention elemental impure finalizer: I'd suggest to avoid them as long as possible. A finalizer with side effects might cause hard to debug behavior when concurrent destruction happens.

@fpenunuri
Copy link
Collaborator

I agree — the finalizer should be avoided if possible. However, in a
main program, a subroutine to explicitly free the allocated variables
may still be necessary.

For instance

program test1
use iso_fortran_env, only : real64
use dnaoad
implicit none

integer, parameter :: prec = real64
integer, parameter :: order = 5
complex(prec) :: z0
type(dual_cmplx64) :: z

z0 = (1.1_prec, 2.2_prec)
call initialize_dual(z, order)
z = z0
call z%set_derivative(1, cmplx(1, 0, prec))
print *, z%get_derivative(1)

! call clear_dual(z) ! <--- Without this, Valgrind reports: possibly lost:
! 136 bytes in 1 block

end program test1

@aradi
Copy link
Collaborator

aradi commented Jul 30, 2025

@fpenunuri Please note, that this message of valgrind is a false positive, allocatables are always warranted to be automatically deallocated when going out of scope, so no memory is lost. The reason you get the valgrind message here, because variables declared in the scope of a program are not explicitly deallocated (or finalized) by Fortran when the program ends, since the operating system will free all the resources associated with the executable anyway. If you want to avoid the valgrind false positive (or if you have a type which must be explicitely finalized), you should declare all your variables either in a subroutine or within a block:

program test1
  use iso_fortran_env, only : real64
  use dnaoad
  implicit none

  integer, parameter :: prec = real64
  integer, parameter :: order = 5

  block
    complex(prec) :: z0
    type(dual_cmplx64) :: z
    
    z0 = (1.1_prec, 2.2_prec)
    call initialize_dual(z, order)
    z = z0
    call z%set_derivative(1, cmplx(1, 0, prec))
    call clear_dual(z)
  end block

end program test1

or alternatively

program test1
  use iso_fortran_env, only : real64
  use dnaoad
  implicit none

  integer, parameter :: prec = real64
  integer, parameter :: order = 5

  call main()

contains

  subroutine main()
    complex(prec) :: z0
    type(dual_cmplx64) :: z
    
    z0 = (1.1_prec, 2.2_prec)
    call initialize_dual(z, order)
    z = z0
    call z%set_derivative(1, cmplx(1, 0, prec))
    call clear_dual(z)
  end subroutine main

end program test1

Both versions deallocate the allocatable components explicitely (when the instances leave the block or the subroutine scope) instead of letting the OS to do it at program exit, so valgrind should give no warnings.

@fpenunuri
Copy link
Collaborator

@aradi That makes sense, thanks. In your experience, what do you think is the
cleanest approach to keep Valgrind completely quiet in these situations?

Would you prefer:

Wrapping everything in a block,

Encapsulating the logic in a subroutine,

Adding a subroutine just to deallocate  variables manually,
or simply doing nothing and letting the false positive remain?

@aradi
Copy link
Collaborator

aradi commented Jul 31, 2025

@fpenunuri I'd go with the first or the second option. The block and subroutine approaches are completely equivalent, so it really depends on your taste which one you prefer. Defining and calling cleanup routines should be avoided IMO as it just adds additional clutter for something which the language is able to do for you automatically anyway. And letting valgrind giving you false positives should be also avoided. as at some point you might ignore a real valgrind error/warning, just because you think, it is the usual false positive...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants