Topic: Debugging. Variables in the stack frame (activation record).

At first I thought this was a subtle bug in the debugger but now I think it is a feature which is possibly quite helpful (and might even be improved upon -- see later).

The following code uses adaptive trapezoidal rule to integrate sin(x) between 0 and pi/2 (of course the result should be 1.0). The main function integrate calls a recursive subroutine trapezium which calls itself to sub-divide the integral interval to achieve a specific tolerance on the result.

The trapezium subroutine is an internal subroutine to integrate. The function integrate is the host of subroutine trapezium. Variables declared in integrate are in scope (may be referenced) when trapezium is called (both initially and for subsequent recursive calls).

When stepping into trapezium in the debugger I note that:

- Host variables min_interval and integrate (the result variable) are listed in the variables panel.
- The other host variables, fnc_lower, fnc_upper, lower, upper, tolerance are not listed.
- Host variable tol is not listed but is, in any case, hidden by the local variable of the same name.

It seems then that host variables are only listed if they are in scope and are actually referenced in trapezium.
This is potentially useful I think.

module quad
   implicit none
   
contains

   function f(x)
      real, intent(in) :: x
      real :: f
      f = sin(x)
   end function f

   function integrate(lower, upper, tolerance)
      real, intent(in) :: lower, upper, tolerance
      real :: integrate, fnc_lower, fnc_upper, min_interval, tol
      min_interval = (upper - lower)/100.0
      fnc_lower = f(lower)
      fnc_upper = f(upper)
      integrate = 0.0
      tol = tolerance
      call trapezium(lower, upper, fnc_lower, fnc_upper, tol)
   contains
      recursive subroutine trapezium(a, b, fna, fnb, tol)
         real, intent(in) :: a, b, fna, fnb, tol
         real :: c, fnc, area1, area2, error
         c = 0.5*(a + b)
         fnc = f(c)
         area1 = 0.5*(fna + fnb)*(b - a)
         area2 = 0.25*(fna + 2.0*fnc + fnb)*(b - a)
         error = (area2 - area1)/3.0
         if (abs(error) < tol .or. b - a < min_interval) then
            integrate = integrate + area2 + error
         else
            call trapezium(a, c, fna, fnc, 0.5*tol)
            call trapezium(c, b, fnc, fnb, 0.5*tol)
         end if
      end subroutine trapezium
   end function integrate
end module quad

program main
   use quad
   real :: pi, area
   pi = 4.0*atan(1.0)
   area = integrate(0.0, pi/2.0, 1.0e-5)
   print *,'Area under curve = ', area
end program main

Now, the missing host variables can be listed by switching to the stack list in the panels window, selecting integrate and then switching back to the variables list.

This is OK, but I wonder if it could be improved upon by allowing the variables from two or more stack frames to be seen at the same time? My simplest view of this is to give access to up to 4 left-hand panels (tiled on top of each other) which users can configure as they see fit.

Views on this are sought.

--
David

2 (edited by davidb 2015-01-28 07:33:17)

Re: Debugging. Variables in the stack frame (activation record).

... however, I don't know why the variable x isn't listed when I step into the quick_sort subroutine in the following example.

Unless dummy arguments in the host subroutine/function are somehow being missed?

module sorting
   implicit none   
contains
   subroutine sort(x)
      real, intent(inout) :: x(:)
      call quick_sort(1,size(x))
   contains
      recursive subroutine quick_sort(l, u)
         integer, intent(in) :: l, u
         integer :: m
         m = (l+u)/2
         print *, x(l), x(u), x(m)
         ! ... the rest of subroutine is omitted 
      end subroutine quick_sort
   end subroutine sort
end module sorting

program main
   use sorting
   real :: x(100)
   x = (/(real(i), i=1, 100)/)
   call sort(x)
end program main
--
David

Re: Debugging. Variables in the stack frame (activation record).

David,

The behavior you're seeing in both examples is most likely because the debugger's Variables list is showing "local" variables rather than "in-scope" variables.  In your second example, the variable x is clearly visible inside quick_sort, but it isn't local to quick_sort.  Therefore, it simply will not appear in the Variables list.

If the debugger did list "in-scope" variables, you'd also see things like public module variables and global variables within functions and subroutines.  I can see how that feature, as an option, would be useful.  However, it would be considerably more difficult to implement due to how Simply Fortran interfaces with the GNU Debugger.

Jeff Armstrong
Approximatrix, LLC

4 (edited by davidb 2015-01-28 18:53:04)

Re: Debugging. Variables in the stack frame (activation record).

jeff wrote:

The behavior you're seeing in both examples is most likely because the debugger's Variables list is showing "local" variables rather than "in-scope" variables.  In your second example, the variable x is clearly visible inside quick_sort, but it isn't local to quick_sort.  Therefore, it simply will not appear in the Variables list.

Unfortunately you are mistaken and this is not how it is working.

In the following code if you break on the line print, * a inside yyy you will see that variable a is listed (despite the fact it is not local to yyy) but array x is not listed. So there is an inconsistency here which is not explained by your comment above and probably needs to be.

When I have been debugging my real codes I am finding that sometimes I can see host variables and sometimes I cannot.

I would urge you to look again and see if you might missed something.

I am not suggesting you make any big changes here just look and see if the variable x should be listed in the same way as a or at least explain why this inconsistency exists.

module foo
   implicit none   
contains
   subroutine xxx(x)
      real, intent(inout) :: x(:)
      real :: a
      a = 1.0
      call yyy(2.0)
   contains
      recursive subroutine yyy(b)
         real, intent(in) :: b
         print *, a
         print *, b
         print *, x(1)
      end subroutine yyy
   end subroutine xxx
end module foo

program main
   use foo
   real :: x(100)
   x = (/(real(i), i=1, 100)/)
   call xxx(x)
end program main
--
David

Re: Debugging. Variables in the stack frame (activation record).

David,

You are correct, it wasn't scoping the variables.  However, the debugger seems to have issues with these scoped arrays.  When you request a list of local variables from the debugger within yyy, the debugger responds with 3:

  • a

  • b

  • x.0

The first two, being scalars, are as one would expect.  The third, however, is a memory reference to the beginning of the x array.  Simply Fortran currently filters and drops these as they are not "useful."   If, using the GNU Debugger command line, one were to request the value of x.0, the debugger will either refuse or it will provide the hexadecimal memory location.  If, again using the GNU Debugger command line, one were to request the value of x, the debugger will show:

numchild="0",value="[0]",type="real(kind=4) (*)"

The above is not only useless, but also incorrect. 

I believe what you're seeing is a result of the not-so-perfect support for the Fortran language in the GNU Debugger.  It seems to be having difficulties with subroutines/functions embedded within other subroutines/functions. 

However, this might also be a result of the Fortran compiler itself. I base this on the fact that the debugger sees the subroutine as foo::yyy, implying that the owner of yyy is the module foo rather than the subroutine foo::xxx.  The compiler may either be transforming the subroutines internally or emitting mildly incorrect debugger information that is subsequently confusing the debugger itself.

The third possibility, and, as I type this, I think it could be correct, is that the compiler is far smarter than you or I are giving it credit for.  It may be seeing that yyy is only using x(1) and is delivering only that value as a local through the artificial x.0.  Even with debugging-safe optimizations or no optimizations, the compiler will still take some steps to "optimize"  away variables.

I'll look into this issue further.

Jeff Armstrong
Approximatrix, LLC