(Quick example: gcc a.c --coverage; ./a.out; gcov a. => a.c.gcov)
gcov computes line execution counts with this approach:
https://github.com/gcc-mirror/gcc/blob/master/gcc/gcov.c#L2684-L2690
/* The user expects the line count to be the number of times
a line has been executed. Simply summing the block count
will give an artificially high number. The Right Thing
is to sum the entry counts to the graph of blocks on this
line, then find the elementary cycles of the local graph
and add the transition counts of those cycles. */
i.e. count from blocks outside the line + cycle count
cycle count is convoluted:
* enumerate cycles using "Finding all the elementary circuits of a directed graph." (Donald B. Johnson)
(gcov.c:circuit); O((V+E)*c) (unfortunately the implementation is slower: O((V+E)*V*c) where V: vertices; E: edges; c: cycles
* for each cycle, handle_cycle() computes the minimum edge and add that to the cycle count
(I don't fully understand its cs_count.)
* (For the curious, its path_contains_zero_or_negative_cycle_arc is strange: 91601 – gcov: ICE in handle_cycle, at gcov.c:699 happen which get code coverage with lcov.)
llvm coverage mapping uses a very different approach (https://github.com/llvm/llvm-project/blob/master/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp#L769)
How do we compute ExecutionCount?