Tracing procedures in your code
You can instruct the compiler to insert calls to the tracing procedures that you have defined to aid in debugging or timing the execution of other procedures.
To trace procedures in your program, you must specify which procedures to trace. You must also provide your own tracing procedures. If you enable tracing without providing tracing procedures, you will get linker errors about undefined symbols called __func_trace_enter, __func_trace_exit, and possibly __func_trace_catch.
Specifying which procedures to trace
The -qfunctrace compiler option controls tracing for all non-inlined user-defined procedures and all outlined compiler-generated procedures in your program. If you are interested in tracing specific external or modules procedures, you can use the -qfunctrace+ and -qfunctrace- compiler options. You can also specify the NOFUNCTRACE directive to disable the tracing of entire modules, external procedures, module procedures, or internal procedures.
What can be traced
Tracing applies to programs, external procedures, non-intrinsic module procedures, and internal procedures.
Compiler-generated procedures are not traced unless they were generated for outlined user code, such as an OpenMP program. In those cases, the name of the outlined procedure contains the name of the original user procedure as a prefix.
Inlined procedures and statement functions cannot be traced because they do not exist in the executable.
To avoid infinite recursion, user-defined tracing procedures cannot be traced. Similarly, tracing must be disabled for procedures called from user-defined tracing procedures.
How to write tracing procedures
You can implement the tracing procedures in Fortran, C, or C++.
SUBROUTINE routine_name(procedure_name, file_name, line_number, id)
USE, INTRINSIC :: iso_c_binding
CHARACTER(*), INTENT(IN) :: procedure_name
CHARACTER(*), INTENT(IN) :: file_name
INTEGER(C_INT), INTENT(IN) :: line_number
TYPE(C_PTR), INTENT(INOUT) :: id
END SUBROUTINE
where routine_name is the
name of an external or module procedure.- Using the -qfunctrace_xlf_enter, -qfunctrace_xlf_exit, or -qfunctrace_xlf_catch compiler options.
- Using the FUNCTRACE_XLF_ENTER, FUNCTRACE_XLF_EXIT, or FUNCTRACE_XLF_CATCH directives.
When you specify these options or directives, XL Fortran generates wrapper procedures called __func_trace_enter, __func_trace_exit, and __func_trace_catch that call your corresponding tracing procedure. These wrappers allow interoperability with C and C++ by converting the dummy arguments from the C prototype to the interface described earlier. routine_name must therefore not be named __func_trace_enter, __func_trace_exit, or __func_trace_catch. In addition, your program must not contain more than one of each of the tracing procedures.
- void __func_trace_enter(const char *const procedure_name, const char *const file_name, int line_number, void **const id);
- void __func_trace_exit(const char *const procedure_name, const char *const file_name, int line_number, void **const id);
- void __func_trace_catch(const char *const procedure_name, const char *const file_name, int line_number, void **const id);
XL Fortran inserts calls to your tracing procedures on procedure entry and exit. It passes the name of the procedure being traced, the name of the file containing the entry or exit point being traced, and the line number. It also passes the address of a static pointer that is initialized to C_NULL_PTR at the beginning of the program. This pointer allows you to store arbitrary data in the entry tracing procedure and access this data in the exit and catch procedures. See the Examples section for detail. Because this pointer resides in static memory, extra steps might be needed when tracing threaded or recursive procedures.
Sample tracing procedures
- tracing_routines.c: Provides tracing procedures written in C. This file is useful when you do not require access to Fortran modules, and when there is a possibility of recursive input / output.
- tracing_routines.f90: Provides tracing procedures written in Fortran. This file is useful when you need access to Fortran modules or intrinsics in your tracing procedures.
> cat helloworld.f
print *, 'hello world'
end
> cc -c /opt/IBM/xlf/15.1.0/samples/functrace/tracing_routines.c
> xlf95 helloworld.f -qfunctrace tracing_routines.o
** _main === End of Compilation 1 ===
1501-510 Compilation successful for file helloworld.f.
> ./a.out
{ _main (helloworld.f:1)
hello world
} _main (helloworld.f:2)
>
Tracing limitations
- A procedure cannot be traced separately from its ENTRY points. Either all are traced or none are. The name of the procedure is passed to the tracing procedure even when tracing the ENTRY point. The line number helps distinguish what is being traced in this case.
- The Fortran standard requires pure procedures to have no side effects. The compiler uses this assumption when optimizing your program. If you enable tracing of a pure procedure, your tracing procedure must not change the program state in a way that creates a side effect.
- The Fortran standard imposes limits on recursive input/output.
If you write your tracing procedures in Fortran, you must be careful
not to break these rules.The following example has a print statement where an I/O item is the result of a function call (foo). It is illegal for the tracing procedure in this case to have I/O on an external file:
> cat recursive.f integer function test() test = 1 end function integer test print *, test() ! test must not have I/O on external unit end > xlf95 -c /opt/IBM/xlf/15.1.0/samples/functrace/tracing_routines.f90 ** my__func_trace_enter === End of Compilation 1 === ** my__func_trace_exit === End of Compilation 2 === ** my__func_trace_catch === End of Compilation 3 === 1501-510 Compilation successful for file tracing_routines.f90. > xlf95 recursive.f tracing_routines.o -qfunctrace ** test === End of Compilation 1 === ** _main === End of Compilation 2 === 1501-510 Compilation successful for file recursive.f. > ./a.out { _main (recursive.f:6) XL Fortran (I/O initialization): I/O recursion detected. IOT/Abort trap >
Note: You can work around this by writing the tracing procedure in C. For an example, see the tracing_routines.c sample file described in section Sample tracing procedures. - When optimizing your program, the compiler reorders code and removes dead code. As a result, the line number passed to the tracing procedure might not be accurate when optimization is enabled.
Examples
> cat example.f
! Designate my_enter as a tracing procedure that should be called
! on procedure entry
!ibm* functrace_xlf_enter
subroutine my_enter(procedure_name, file_name, line_number, id)
use, intrinsic :: iso_c_binding
use, intrinsic :: xlfutility
character(*), intent(in) :: procedure_name, file_name
integer(c_int), intent(in) :: line_number
type(c_ptr), intent(inout) :: id
integer(kind=time_size), pointer :: enter_count
! Store the time we entered the procedure being traced into id.
if (.not. c_associated(id)) then
allocate(enter_count)
enter_count = time_()
id = c_loc(enter_count)
end if
print *, 'Entered procedure ', procedure_name, ' at ( ', &
file_name, ' :', line_number, ').'
end subroutine
! Designate my_exit as a tracing procedure that should be called
! on procedure exit
!ibm* functrace_xlf_exit
subroutine my_exit(procedure_name, file_name, line_number, id)
use, intrinsic :: iso_c_binding
use, intrinsic :: xlfutility
character(*), intent(in) :: procedure_name, file_name
integer(c_int), intent(in) :: line_number
type(c_ptr), intent(inout) :: id
integer(kind=time_size), pointer :: enter_count
integer(kind=time_size) exit_count, duration
! id should have been associated in my_enter with the time we
! entered the procedure being traced. Find the elapsed time.
if (c_associated(id)) then
exit_count = time_()
call c_f_pointer(id, enter_count)
duration = exit_count - enter_count
else
stop "error!"
endif
print *, 'Leaving procedure ', procedure_name, ' at ( ', &
file_name, ' :', line_number, ').'
print *, 'Spent', duration, 'seconds in ', procedure_name, '.'
end subroutine
! sub2 will be traced
subroutine sub2
call sleep_(3)
end subroutine
! sub1 will be traced
subroutine sub1
call sleep_(5)
call sub2
end subroutine
! Do not want to trace main_program
!ibm* nofunctrace
program main_program
call sub1
end program
> xlf95 example.f -qfunctrace
** my_enter === End of Compilation 1 ===
** my_exit === End of Compilation 2 ===
** sub2 === End of Compilation 3 ===
** sub1 === End of Compilation 4 ===
** main_program === End of Compilation 5 ===
1501-510 Compilation successful for file example.f.
> ./a.out
Entered procedure sub1 at ( example.f : 59 ).
Entered procedure sub2 at ( example.f : 54 ).
Leaving procedure sub2 at ( example.f : 55 ).
Spent 3 seconds in sub2.
Leaving procedure sub1 at ( example.f : 61 ).
Spent 8 seconds in sub1.
>
Related information
- For details about the -qfunctrace compiler option, see -qfunctrace in the XL Fortran Compiler Reference.
- For details about -qfunctrace_xlf_catch, -qfunctrace_xlf_enter, or -qfunctrace_xlf_exit compiler options, see the Detailed descriptions of the XL Fortran compiler options section in the XL Fortran Compiler Reference.
- For details about the FUNCTRACE_XLF_CATCH, FUNCTRACE_XLF_ENTER, and FUNCTRACE_XLF_EXIT directives, see Detailed directive descriptions section in the XL Fortran Language Reference.
- For details about the NOFUNCTRACE directive, see NOFUNCTRACE in the XL Fortran Language Reference.