The compiler uses the static information to derive as precise information as possible with as little instrumentation as required. For example, if the loop bounds of a given array are not known at compile time but they depend on variables that do not change through the execution of the loop (i.e., they are symbolically constant), the compiler needs only to capture the value of the bounds once and determine at run-time (with the help of an auxiliary library routine) what the actual bounds are. In extreme cases, however, the compiler may not be able to ascertain if a given expression is symbolically constant, it thus resorts to a very simplistic instrumentation that keeps track of every single loop iteration execution by inserting a counter in the transformed code. The various levels of instrumentation at the source-code level are depicted in figure 3 below.
741: do 10 i = 1, n
742: …
743: 10 continue
740: call SaveLoopBounds(1,n)
741: do 10 i = 1, n
742: …
743: 10 continue
747: do 10 k=c(j),c(j+1)-1
748: …
749: 10 continue
746: call saveLoopBounds(c(j),c(j+1)-1)
747: do 10 k = c(j),c(j+1)-1
748: …
749: 10 continue
747: 100
748: …
749: goto 100
746: call setCurentLoop(loopid)
747: 100 call saveLoopIteration()
748: …
749: goto 100
This instrumentation focuses on capturing metrics at the basic block level. For this reason, the compiler assigns a unique identifier to each basic block, thus allowing a post-processing tool to gather the various basic block metrics and convolve the corresponding execution frequencies and symbolic array information for analysis. Overall, this source level instrumentation is accomplished by the declaration of extra counter variables and library calls whose placement and arguments are determined by the compiler analysis.