LCOV - code coverage report
Current view: top level - gcc - path-coverage.cc (source / functions) Coverage Total Hit
Test: gcc.info Lines: 91.7 % 301 276
Test Date: 2026-02-28 14:20:25 Functions: 94.1 % 17 16
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /* Calculate prime path coverage.
       2              :    Copyright (C) 2024-2026 Free Software Foundation, Inc.
       3              : 
       4              : This file is part of GCC.
       5              : 
       6              : GCC is free software; you can redistribute it and/or modify it under
       7              : the terms of the GNU General Public License as published by the Free
       8              : Software Foundation; either version 3, or (at your option) any later
       9              : version.
      10              : 
      11              : GCC is distributed in the hope that it will be useful, but WITHOUT ANY
      12              : WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13              : FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14              : for more details.
      15              : 
      16              : You should have received a copy of the GNU General Public License
      17              : along with GCC; see the file COPYING3.  If not see
      18              : <http://www.gnu.org/licenses/>.  */
      19              : 
      20              : #include "config.h"
      21              : #include "system.h"
      22              : #include "coretypes.h"
      23              : #include "diagnostic-core.h"
      24              : #include "memmodel.h"
      25              : #include "target.h"
      26              : #include "function.h"
      27              : #include "basic-block.h"
      28              : #include "tree.h"
      29              : #include "tree-cfg.h"
      30              : #include "tree-nested.h"
      31              : #include "cfg.h"
      32              : #include "gimple.h"
      33              : #include "gimple-iterator.h"
      34              : #include "gimplify.h"
      35              : #include "coverage.h"
      36              : #include "ssa.h"
      37              : #include "vec.h"
      38              : #include "sbitmap.h"
      39              : #include "graphds.h"
      40              : #include "hash-map.h"
      41              : #include "bitmap.h"
      42              : #include "cfganal.h"
      43              : 
      44              : bool mark_dfs_back_edges (struct function *);
      45              : vec<vec<int>> prime_paths (struct graph*, size_t);
      46              : 
      47              : namespace
      48              : {
      49              : 
      50              : /* Check if all the successors of BB are abnormal, e.g. setjmp.  */
      51              : bool
      52          595 : all_abnormal_succs_p (basic_block bb)
      53              : {
      54         1254 :   for (edge e : bb->succs)
      55          325 :     if (!(e->flags & EDGE_ABNORMAL))
      56              :       return false;
      57              :   return true;
      58              : }
      59              : 
      60              : /* Build a struct graph equivalent to the CFG for the function FN.  The caller
      61              :    must free the returned graph with free_graph.  The data field of every
      62              :    vertex and edge point to the basic blocks and edges in the CFG.
      63              : 
      64              :    The CFG recording and gcov is not aware of abnormal edges, so they are
      65              :    ignored here, too.  This means some paths are lost, e.g. those that involve
      66              :    setjmp/longjmp.  They are still paths but would need more support from
      67              :    profile.cc and gcov.cc to work.  */
      68              : struct graph*
      69          288 : cfg_as_graph (struct function* fn)
      70              : {
      71          288 :   struct graph *g = new_graph (n_basic_blocks_for_fn (fn));
      72          288 :   basic_block entry = ENTRY_BLOCK_PTR_FOR_FN (fn);
      73          288 :   basic_block exit = EXIT_BLOCK_PTR_FOR_FN (fn);
      74              : 
      75          288 :   g->vertices[entry->index].data = entry;
      76          288 :   g->vertices[exit->index].data = exit;
      77              : 
      78          288 :   const unsigned ignore = EDGE_FAKE | EDGE_ABNORMAL | EDGE_ABNORMAL_CALL;
      79          288 :   basic_block bb;
      80         2550 :   FOR_EACH_BB_FN (bb, fn)
      81              :     {
      82         2262 :       g->vertices[bb->index].data = bb;
      83        10138 :       for (edge e : bb->succs)
      84         3352 :         if (!(e->flags & ignore) && e->dest != exit)
      85         2986 :           add_edge (g, e->src->index, e->dest->index)->data = e;
      86              :     }
      87          288 :   return g;
      88              : }
      89              : 
      90              : /* Check if BB's predecessor is the ENTRY_BLOCK, i.e. BB is the first real
      91              :    block in the function.  */
      92              : bool
      93          631 : pred_entry_p (const_basic_block bb)
      94              : {
      95          945 :   return single_pred_p (bb) && single_pred (bb)->index == ENTRY_BLOCK;
      96              : }
      97              : 
      98              : /* Find the prime paths for the function FN with the ENTRY and EXIT blocks
      99              :    removed.  This can lead to empty paths when there is a fake edge to the exit
     100              :    block, for example for abort functions or infinite loops.  Empty paths are
     101              :    removed because the length of the returned vec is important.  */
     102              : vec<vec<int>>
     103          288 : find_prime_paths (struct function *fn)
     104              : {
     105          288 :   struct graph *cfg = cfg_as_graph (fn);
     106          288 :   vec<vec<int>> paths = prime_paths (cfg, path_coverage_limit);
     107              : 
     108          288 :   bool any_empty = false;
     109         3359 :   for (vec<int> &path : paths)
     110              :     {
     111              :       /* setjmp calls will be isolated basic blocks when ABNORMAL_EDGEs are
     112              :          removed.  If a path is made up of just such a vertex it is pointless
     113              :          and can be removed.  If a function is only __builtin_exit() (see
     114              :          gcov-22.C) the CFG will only have one block and look like such an
     115              :          island, and we want to preserve it.  */
     116         2501 :       if (path.length () == 1
     117          631 :           && !pred_entry_p (BASIC_BLOCK_FOR_FN (fn, path[0]))
     118         3096 :           && all_abnormal_succs_p (BASIC_BLOCK_FOR_FN (fn, path[0])))
     119          309 :         path.pop ();
     120         4693 :       if (!path.is_empty () && path[0] == ENTRY_BLOCK)
     121          285 :         path.ordered_remove (0);
     122         4408 :       if (!path.is_empty () && path.last () == EXIT_BLOCK)
     123            0 :         path.pop ();
     124              : 
     125         5002 :       if (path.is_empty ())
     126              :         {
     127          594 :           any_empty = true;
     128          594 :           path.release ();
     129              :         }
     130              :     }
     131              : 
     132          288 :   unsigned ix1, ix2;
     133          288 :   vec<int> *ptr;
     134          288 :   if (any_empty)
     135         2786 :     VEC_ORDERED_REMOVE_IF (paths, ix1, ix2, ptr, ptr->is_empty ());
     136              : 
     137          288 :   return paths;
     138              : }
     139              : 
     140              : /* Return the edge between SRC and DST.  */
     141              : edge
     142         8507 : edge_between (struct function *fn, int src, int dst)
     143              : {
     144         8507 :   basic_block bbsrc = BASIC_BLOCK_FOR_FN (fn, src);
     145         8507 :   basic_block bbdst = BASIC_BLOCK_FOR_FN (fn, dst);
     146        49504 :   for (edge e : bbsrc->succs)
     147        32490 :     if (e->dest == bbdst)
     148         8507 :       return e;
     149            0 :   gcc_unreachable ();
     150              : }
     151              : 
     152              : /* Get the basic blocks of FN in topological order so that all predecessors
     153              :    come before the vertex, while ignoring back edges.  */
     154              : vec<basic_block>
     155          285 : topsort (struct function *fn)
     156              : {
     157          285 :   vec<basic_block> blocks {};
     158          285 :   auto_vec<int> order {};
     159          285 :   blocks.reserve (n_basic_blocks_for_fn (fn));
     160          285 :   order.safe_grow (n_basic_blocks_for_fn (fn));
     161              : 
     162          285 :   const int n = post_order_compute (order.address (), false, false);
     163         2325 :   for (int i = 0; i < n; ++i)
     164         2040 :     blocks.quick_push (BASIC_BLOCK_FOR_FN (fn, order[i]));
     165          285 :   blocks.reverse ();
     166          285 :   return blocks;
     167          285 : }
     168              : 
     169              : /* Hasher for maps where the key is a pair of edge/basic_block and bucket id
     170              :    (size_t).  */
     171              : template <typename T>
     172              : using bucket_hash = pair_hash <nofree_ptr_hash <T>,
     173              :                                int_hash <size_t, size_t(-1)>>;
     174              : 
     175              : /* Hasher for {edge, bucket-id} keys.  */
     176              : using edge_hash = bucket_hash <edge_def>;
     177              : /* Hasher for {basic_block, bucket-id} keys.  */
     178              : using block_hash = bucket_hash <basic_block_def>;
     179              : 
     180              : /* Find the union of the bitsets sets on the incoming edges of BB at BUCKET.
     181              :    If no paths go through BB the returned bitset would be empty.  Bitsets are
     182              :    looked up in ANDS, and if the nth bit is set then the nth path contains that
     183              :    edge.  */
     184              : uint64_t
     185         2768 : union_incoming_bit_and (hash_map <edge_hash, uint64_t> &ands,
     186              :                         const basic_block bb, size_t bucket)
     187              : {
     188         2768 :   uint64_t inc = 0;
     189        12767 :   for (edge e : bb->preds)
     190              :     {
     191         4463 :       const uint64_t *val = ands.get ({e, bucket});
     192         4463 :       inc |= (val ? *val : 0);
     193              :     }
     194         2768 :   return inc;
     195              : }
     196              : 
     197              : 
     198              : /* Check if the incoming edges have the power to modify the paths in flight for
     199              :    BUCKET.  If all the incoming edges would apply the same bitmask the
     200              :    BB+BUCKET will not actually update the path set, and we don't need to emit
     201              :    an AND_EXPR and have a later fork distinguish the paths.  */
     202              : bool
     203          591 : can_change_path_p (hash_map <edge_hash, uint64_t> &ands,
     204              :                    const basic_block bb, size_t bucket, uint64_t all_and)
     205              : {
     206         2030 :   for (edge e : bb->preds)
     207              :     {
     208          625 :       const uint64_t *e_and = ands.get ({e, bucket});
     209          625 :       if (!e_and || *e_and != all_and)
     210          591 :         return true;
     211              :     }
     212              :   return false;
     213              : }
     214              : 
     215              : /* Check if all bits in BITSET are 1 for the target size TARGET_SIZE.  For a
     216              :    32-bit target only the bits in the lower half should be set, and this should
     217              :    return true when all bits in the lower half are set, even if the bitset type
     218              :    have room for more bits.  */
     219              : bool
     220         1857 : all_bits_set_p (uint64_t bitset, size_t target_size)
     221              : {
     222         3034 :   return (size_t)popcount_hwi (bitset) == target_size;
     223              : }
     224              : 
     225              : /* Create a constant or SSA name of GCOV_TYPE_NODE type and zero-assign to it
     226              :    safely on the edge E.  If the edge is abnormal it is assumed the phi is
     227              :    abnormal and we need an SSA name, otherwise fall back to a constant.  The
     228              :    returned value is safe to use with add_phi_arg ().
     229              : 
     230              :    If the edge is abnormal we cannot insert on it directly, and instead
     231              :    carefully add the assignment on the source block.  If that source block ends
     232              :    with control flow (like those produced by _setjmp) we must insert before to
     233              :    not break that invariant, otherwise insert after so that things like the
     234              :    setjmp receiver is the first element of the basic block.  Doing the assign
     235              :    is necessary as phis cannot resolve to constants.  */
     236              : tree
     237          726 : safe_insert_zero_phi_arg (edge e, tree gcov_type_node)
     238              : {
     239          726 :   tree cst = build_zero_cst (gcov_type_node);
     240          726 :   if (!(e->flags & (EDGE_ABNORMAL | EDGE_ABNORMAL_CALL)))
     241              :     return cst;
     242              : 
     243           43 :   tree zero = make_ssa_name (gcov_type_node);
     244           43 :   gassign *put = gimple_build_assign (zero, cst);
     245           43 :   gimple_stmt_iterator gsi = gsi_start_nondebug_after_labels_bb (e->src);
     246           43 :   if (gimple_call_internal_p (*gsi, IFN_ABNORMAL_DISPATCHER)
     247           43 :       || stmt_ends_bb_p (*gsi))
     248           21 :     gsi_insert_before (&gsi, put, GSI_NEW_STMT);
     249              :   else
     250           22 :     gsi_insert_after (&gsi, put, GSI_NEW_STMT);
     251              : 
     252              :   return zero;
     253              : }
     254              : 
     255              : /* Check if SSA is a constant value (created by build_int_cst) and can be
     256              :    folded.  */
     257              : bool
     258         1132 : constant_p (tree ssa)
     259              : {
     260         1132 :   return tree_fits_uhwi_p (ssa);
     261              : }
     262              : 
     263              : /* A fixup task.  When resolving the exit SSA for a back edge arg to a phi
     264              :    node, the exit SSA has not been created yet.  Record what needs to be done
     265              :    when it is created, and tie the phi to the right SSA name once it is
     266              :    guaranteed it is created.  If MASK is nullptr the predecessor's SSA should
     267              :    be used as-is, and does not need an AND.  This should only be created with
     268              :    the helpers fixup_noop and fixup_and.  */
     269              : struct fixup
     270              : {
     271              :   gphi *phi;
     272              :   edge e;
     273              :   tree lhs;
     274              :   tree mask;
     275              :   size_t bucket;
     276              : };
     277              : 
     278              : /* Create a fixup with a no-op for the PHI in BUCKET.  Use this when the edge E
     279              :    does not need an AND applied and should rather propagate the predecessor's
     280              :    SSA name.  */
     281              : fixup
     282            0 : fixup_noop (gphi *phi, edge e, size_t bucket)
     283              : {
     284            0 :   fixup todo;
     285            0 :   todo.phi = phi;
     286            0 :   todo.e = e;
     287            0 :   todo.bucket = bucket;
     288            0 :   todo.lhs = nullptr;
     289            0 :   todo.mask = nullptr;
     290            0 :   return todo;
     291              : }
     292              : 
     293              : /* Create a fixup for PHI through BUCKET with the exit SSA name E->src ANDed
     294              :    with MASK (E->src & MASK).  GCOV_TYPE_NODE should be a tree of the gcov type
     295              :    node for creating SSA names.  */
     296              : fixup
     297           63 : fixup_and (gphi *phi, edge e, size_t bucket, uint64_t mask,
     298              :            tree gcov_type_node)
     299              : {
     300           63 :   fixup todo;
     301           63 :   todo.phi = phi;
     302           63 :   todo.e = e;
     303           63 :   todo.bucket = bucket;
     304           63 :   todo.lhs = make_ssa_name (gcov_type_node);
     305           63 :   todo.mask = build_int_cst (gcov_type_node, mask);
     306           63 :   return todo;
     307              : }
     308              : 
     309              : /* Insert LOCAL |= MASK on BB safely, even when BB is a returns_twice block.
     310              : 
     311              :    This is a helper to safely emit code for updating the path bitmask in the
     312              :    presence of abnormal edges and returns_twice blocks, since they have special
     313              :    restrictions on edge splits and first/last instructions on the block.  */
     314              : tree
     315          173 : safe_insert_ior (basic_block bb, tree local, tree mask, gphi *phi,
     316              :                  tree gcov_type_node)
     317              : {
     318          173 :   gimple_stmt_iterator gsi = gsi_start_nondebug_after_labels_bb (bb);
     319          173 :   gimple *stmt = gsi_stmt (gsi);
     320              : 
     321              :   /* This is a returns_twice block (e.g. setjmp) which does not really expect
     322              :      anything before or after, so we cannot insert the IOR on the block itself.
     323              :      We move the IORs to the predecessors instead and update the phi.  The
     324              :      abnormal edge cannot be split, so in that case we carefully put the IOR
     325              :      after the ABNORMAL_DISPATCHER.  */
     326          157 :   if (stmt && is_gimple_call (stmt)
     327          199 :       && (gimple_call_flags (stmt) & ECF_RETURNS_TWICE))
     328              :     {
     329           88 :       for (edge e : bb->preds)
     330              :         {
     331           37 :           gcc_checking_assert (phi);
     332           37 :           tree next = make_ssa_name (gcov_type_node);
     333           37 :           tree prev = gimple_phi_arg_def_from_edge (phi, e);
     334           37 :           gassign *put = prev
     335           37 :             ? gimple_build_assign (next, BIT_IOR_EXPR, prev, mask)
     336            1 :             : gimple_build_assign (next, mask);
     337              : 
     338           37 :           gimple_stmt_iterator gsi = gsi_last_bb (e->src);
     339           37 :           add_phi_arg (phi, next, e, UNKNOWN_LOCATION);
     340              : 
     341           37 :           if (e->flags & (EDGE_ABNORMAL | EDGE_ABNORMAL_CALL))
     342           17 :             gsi_insert_before (&gsi, put, GSI_SAME_STMT);
     343              :           else
     344           20 :             gsi_insert_on_edge (e, put);
     345              :         }
     346              :     }
     347              :   else
     348              :     {
     349          156 :       tree next = make_ssa_name (gcov_type_node);
     350          156 :       gassign *put = gimple_build_assign (next, BIT_IOR_EXPR, local, mask);
     351          156 :       gsi_insert_before (&gsi, put, GSI_SAME_STMT);
     352          156 :       local = next;
     353              :     }
     354          173 :   return local;
     355              : }
     356              : 
     357              : /* Insert instructions updating the global counter at BUCKET with the contents
     358              :    of (LOCAL & MASK).  LOCAL is the SSA counter for this bucket, MASK the bits
     359              :    that should be updated (that is, the paths that end in this basic block).
     360              :    If ATOMIC_IOR is non-null the it uses atomics.  GCOV_TYPE_NODE should be a
     361              :    tree of the gcov type node for creating SSA names.
     362              : 
     363              :    global[BUCKET] |= (LOCAL & MASK)
     364              : 
     365              :    If MASK is null, no mask is applied and it becomes:
     366              : 
     367              :    global[BUCKET] |= LOCAL
     368              : 
     369              :    This function should only be necessary for the successor of an
     370              :    ABNORMAL_DISPATCHER e.g. the destination of a longjmp.  Paths starting at a
     371              :    longjmp do not have anything to flush, so those edges are simply ignored.
     372              :    Since this is a returns_twice block we cannot put anything before (or really
     373              :    after), so we instrument on the edge itself, rather than messing with
     374              :    splitting and changing the graph now.
     375              : 
     376              :    This is similar to gsi_safe_insert_before, but this function does not
     377              :    immediately commit edge inserts and does not split blocks.  */
     378              : void
     379           18 : flush_on_edges (basic_block bb, size_t bucket, tree local, tree mask,
     380              :                 tree atomic_ior, tree gcov_type_node)
     381              : {
     382           18 :   gimple *def = !constant_p (local) ? SSA_NAME_DEF_STMT (local) : NULL;
     383           17 :   gphi *phi = safe_dyn_cast <gphi *> (def);
     384              : 
     385           18 :   tree relaxed = nullptr;
     386           18 :   if (atomic_ior)
     387            0 :     relaxed = build_int_cst (integer_type_node, MEMMODEL_RELAXED);
     388              : 
     389           93 :   for (edge e : bb->preds)
     390              :     {
     391           39 :       if (e->flags & (EDGE_ABNORMAL | EDGE_ABNORMAL_CALL))
     392           18 :         continue;
     393              : 
     394           21 :       tree global = tree_coverage_counter_ref (GCOV_COUNTER_PATHS, bucket);
     395           21 :       if (phi)
     396           20 :         local = gimple_phi_arg_def_from_edge (phi, e);
     397              : 
     398           21 :       tree global_copy = make_ssa_name (gcov_type_node);
     399           21 :       gassign *ga1 = gimple_build_assign (global_copy, global);
     400           21 :       gsi_insert_on_edge (e, ga1);
     401              : 
     402           21 :       tree masked;
     403           21 :       if (mask)
     404              :         {
     405           21 :           masked = make_ssa_name (gcov_type_node);
     406           21 :           gassign *ga2 = gimple_build_assign (masked, BIT_AND_EXPR, local,
     407              :                                               mask);
     408           21 :           gsi_insert_on_edge (e, ga2);
     409              :         }
     410              :       else
     411              :         masked = local;
     412              : 
     413           21 :       if (atomic_ior)
     414              :         {
     415            0 :           global = unshare_expr (global);
     416            0 :           gcall *call = gimple_build_call (atomic_ior, 3, build_addr (global),
     417              :                                            masked, relaxed);
     418            0 :           gsi_insert_on_edge (e, call);
     419              :         }
     420              :       else
     421              :         {
     422           21 :           tree tmp = make_ssa_name (gcov_type_node);
     423           21 :           gassign *ga3 = gimple_build_assign (tmp, BIT_IOR_EXPR, global_copy,
     424              :                                               masked);
     425           21 :           gassign *ga4 = gimple_build_assign (unshare_expr (global), tmp);
     426           21 :           gsi_insert_on_edge (e, ga3);
     427           21 :           gsi_insert_on_edge (e, ga4);
     428              :         }
     429              :     }
     430           18 : }
     431              : 
     432              : /* Insert instructions update the global counter at BUCKET with the contents of
     433              :    (LOCAL & MASK).  LOCAL is the SSA counter for this bucket, MASK the bits
     434              :    that should be updated (that is, the paths that end in this basic block).
     435              :    If ATOMIC_IOR is non-null the it uses atomics.  GCOV_TYPE_NODE should be a
     436              :    tree of the gcov type node for creating SSA names.
     437              : 
     438              :    global[BUCKET] |= (LOCAL & MASK)
     439              : 
     440              :    If MASK is null, no mask is applied and it becomes:
     441              : 
     442              :    global[BUCKET] |= LOCAL
     443              : 
     444              :    This function should be used on every block except returns_twice blocks (see
     445              :    flush_on_edge) as all incoming edges can use the same flushing code.  */
     446              : void
     447          489 : flush_on_gsi (gimple_stmt_iterator *gsi, size_t bucket, tree local, tree mask,
     448              :               tree atomic_ior, tree gcov_type_node)
     449              : {
     450          489 :   tree relaxed = nullptr;
     451          489 :   if (atomic_ior)
     452           86 :     relaxed = build_int_cst (integer_type_node, MEMMODEL_RELAXED);
     453          489 :   tree global = tree_coverage_counter_ref (GCOV_COUNTER_PATHS, bucket);
     454              : 
     455          489 :   tree global_copy = make_ssa_name (gcov_type_node);
     456          489 :   gassign *ga1 = gimple_build_assign (global_copy, global);
     457          489 :   gsi_insert_before (gsi, ga1, GSI_SAME_STMT);
     458              : 
     459          489 :   tree masked;
     460          489 :   if (mask)
     461              :     {
     462          479 :       masked = make_ssa_name (gcov_type_node);
     463          479 :       gassign *ga2 = gimple_build_assign (masked, BIT_AND_EXPR, local, mask);
     464          479 :       gsi_insert_before (gsi, ga2, GSI_SAME_STMT);
     465              :     }
     466              :   else
     467              :     masked = local;
     468              : 
     469          489 :   if (atomic_ior)
     470              :     {
     471           86 :       global = unshare_expr (global);
     472           86 :       gcall *call = gimple_build_call (atomic_ior, 3, build_addr (global),
     473              :                                        masked, relaxed);
     474           86 :       gsi_insert_before (gsi, call, GSI_SAME_STMT);
     475              :     }
     476              :   else
     477              :     {
     478          403 :       tree tmp = make_ssa_name (gcov_type_node);
     479          403 :       gassign *ga3 = gimple_build_assign (tmp, BIT_IOR_EXPR, global_copy,
     480              :                                           masked);
     481          403 :       gassign *ga4 = gimple_build_assign (unshare_expr (global), tmp);
     482          403 :       gsi_insert_before (gsi, ga3, GSI_SAME_STMT);
     483          403 :       gsi_insert_before (gsi, ga4, GSI_SAME_STMT);
     484              :     }
     485          489 : }
     486              : 
     487              : } // namespace
     488              : 
     489              : 
     490              : /* Instrument FN for prime path coverage, enabled by -fpath-coverage.  The
     491              :    number of paths grows very fast with the complexity (control flow) which
     492              :    explodes compile times and object file size.  Giving up is controlled by the
     493              :    -fpath-coverage-limit flag.  The paths are sorted lexicographically by basic
     494              :    block id, and each path is identified by its index in the sorted set of
     495              :    paths, which in turn corresponds to a bit in a large bitset associated with
     496              :    FN.  The monitoring code is a few bitwise operations added to edges and
     497              :    basic blocks to maintain a set of live paths (note that many paths may
     498              :    overlap and that many paths may be covered by the same input).  When
     499              :    reaching the final vertex of a path the global counters are updated.
     500              : 
     501              :    This is made more complicated by the gcov type having very limited size.  To
     502              :    support functions with more than 32/64 paths the bitmap is implemented on
     503              :    top of a sequence of gcov integers, "buckets", where path N is recorded as
     504              :    bit N%64 in bucket N/64.  For large functions, an individual basic block
     505              :    will only be part of a small subset of paths, and by extension buckets and
     506              :    local counters.  Only the necessary counters are read and written.  */
     507              : unsigned
     508          288 : instrument_prime_paths (struct function *fn)
     509              : {
     510          288 :   mark_dfs_back_edges (fn);
     511          288 :   vec<vec<int>> paths = find_prime_paths (fn);
     512              : 
     513          288 :   if (paths.is_empty ())
     514              :     {
     515            3 :       warning_at (fn->function_start_locus, OPT_Wcoverage_too_many_paths,
     516              :                   "paths exceeding limit, giving up path coverage");
     517            3 :       release_vec_vec (paths);
     518            3 :       return 0;
     519              :     }
     520              : 
     521          285 :   tree gcov_type_node = get_gcov_type ();
     522          285 :   const size_t bucketsize = TYPE_PRECISION (gcov_type_node);
     523          285 :   const size_t nbuckets = (paths.length () + (bucketsize - 1)) / bucketsize;
     524          285 :   gcc_checking_assert (sizeof (uint64_t) * BITS_PER_UNIT >= bucketsize);
     525              : 
     526          285 :   if (!coverage_counter_alloc (GCOV_COUNTER_PATHS, nbuckets))
     527              :     {
     528            0 :       release_vec_vec (paths);
     529            0 :       return 0;
     530              :     }
     531              : 
     532          285 :   hash_map <edge_hash, uint64_t> ands;
     533          285 :   hash_map <block_hash, uint64_t> iors;
     534          285 :   hash_map <block_hash, uint64_t> flushes;
     535              : 
     536              :   /* Now that we have all paths we must figure out what bitmasks must be
     537              :      applied on the edges.  We also record in which basic block the path starts
     538              :      (e.g. accumulator resets) and ends (accumulator flushes).  */
     539         2192 :   for (size_t pathno = 0; pathno != paths.length (); ++pathno)
     540              :     {
     541         1907 :       const vec<int> &path = paths[pathno];
     542         1907 :       const size_t bucket = pathno / bucketsize;
     543         1907 :       const uint64_t bit = uint64_t (1) << (pathno % bucketsize);
     544              : 
     545         1907 :       basic_block first = BASIC_BLOCK_FOR_FN (fn, path[0]);
     546         1907 :       basic_block last = BASIC_BLOCK_FOR_FN (fn, path[path.length () - 1]);
     547              : 
     548        20828 :       for (unsigned i = 1; i != path.length (); ++i)
     549              :         {
     550         8507 :           edge e = edge_between (fn, path[i-1], path[i]);
     551         8507 :           ands.get_or_insert ({e, bucket}) |= bit;
     552              :         }
     553              : 
     554         1907 :       iors.get_or_insert ({first, bucket}) |= bit;
     555         1907 :       flushes.get_or_insert ({last, bucket}) |= bit;
     556              :     }
     557              : 
     558              :   /* The basic blocks (except entry, exit) for this function, in topological
     559              :      order.  Processing in this order means that the predecessor(s) exit SSAs
     560              :      will have been created by the time a block is processed, unless it is a
     561              :      loop/back edge.  This simplifies processing a bit.  */
     562          285 :   vec<basic_block> blocks = topsort (fn);
     563              : 
     564              :   /* The exit SSA names for the BLOCK, the SSA name the BLOCK successors should
     565              :      use as inputs.  */
     566          285 :   hash_map<block_hash, tree> SSAex;
     567              :   /* The entry SSA name for the BLOCK.  This name forms the basis for the
     568              :      flushing to the global accumulators.  In the presence of phi nodes this is
     569              :      the resolved phi, otherwise it is some predecessor's exit SSA name.  */
     570          285 :   hash_map<block_hash, tree> SSAen;
     571              : 
     572          285 :   auto_vec<fixup, 4> todos;
     573              : 
     574              :   /* Generate the operations for each basic block.  */
     575         2895 :   for (basic_block bb : blocks)
     576              :     {
     577         4808 :       for (size_t bucket = 0; bucket != nbuckets; ++bucket)
     578              :         {
     579         2768 :           tree ssa = nullptr;
     580         2768 :           gphi *phi = nullptr;
     581         2768 :           uint64_t all_and = union_incoming_bit_and (ands, bb, bucket);
     582              : 
     583         2768 :           if (all_and && single_pred_p (bb))
     584              :             {
     585              :               /* There is a path on this edge through the bucket, but since
     586              :                  there is only one predecessor there it has no decisive power.
     587              :                  Just push the predecessor's exit and have later ANDs sort it
     588              :                  out.  */
     589         1535 :               tree *prev = SSAex.get ({single_pred (bb), bucket});
     590         1535 :               gcc_checking_assert (prev);
     591         1535 :               ssa = *prev;
     592              :             }
     593         1233 :           else if (all_and)
     594              :             {
     595              :               /* There are multiple predecessors, so we need a phi.  */
     596          251 :               ssa = make_ssa_name (gcov_type_node);
     597          251 :               phi = create_phi_node (ssa, bb);
     598              :             }
     599              : 
     600         2768 :           if (ssa)
     601         1786 :             SSAen.put ({bb, bucket}, ssa);
     602              : 
     603         2768 :           if (single_pred_p (bb) && single_succ_p (bb))
     604              :             {
     605              :               /* Straight line -- the AND mask will already have been applied
     606              :                  to the first ancestor of this chain, so we don't need to apply
     607              :                  it here.  */
     608              :             }
     609          591 :           else if (!can_change_path_p (ands, bb, bucket, all_and))
     610              :             {
     611              :               /* All incoming edges would apply the same mask, so applying the
     612              :                  AND here would not actually distinguish paths.  Such an AND
     613              :                  will be applied later anyway so we don't need to apply it
     614              :                  here.  This is a huge improvement for large programs.  */
     615              :             }
     616         3167 :           else for (edge e : bb->preds)
     617              :             {
     618         2063 :               const uint64_t *bitmask = ands.get ({e, bucket});
     619              :               /* There is no phi, and there are no paths through this bucket.
     620              :                  Set the SSA name to nullptr so we don't contaminate it by
     621              :                  pushing unrelated predecessors.  */
     622         2063 :               if (!bitmask && !phi)
     623          160 :                 ssa = nullptr;
     624         1903 :               else if (!bitmask && phi)
     625              :                 {
     626          726 :                   tree zero = safe_insert_zero_phi_arg (e, gcov_type_node);
     627          726 :                   add_phi_arg (phi, zero, e, UNKNOWN_LOCATION);
     628              :                 }
     629         1177 :               else if (all_bits_set_p (*bitmask, bucketsize) && !phi)
     630              :                 {
     631              :                   /* This reduces to a no-op (x & ~0) and there is no phi
     632              :                      selection, so just push the SSA.  */
     633            0 :                   gcc_checking_assert (ssa);
     634              :                 }
     635         1177 :               else if (all_bits_set_p (*bitmask, bucketsize) && phi)
     636              :                 {
     637              :                   /* This reduces to a no-op (x & ~0).  Reusing the SSA and not
     638              :                      emitting an unecessary AND is a big improvement for large
     639              :                      programs.  */
     640            0 :                   tree *prev = SSAex.get ({e->src, bucket});
     641            0 :                   if (prev)
     642            0 :                     add_phi_arg (phi, *prev, e, UNKNOWN_LOCATION);
     643              :                   else
     644            0 :                     todos.safe_push (fixup_noop (phi, e, bucket));
     645              :                 }
     646         1177 :               else if (SSAex.get ({e->src, bucket}))
     647              :                 {
     648              :                   /* We need to apply a mask, folding if possible.  If there is
     649              :                      a phi it is already the latest-touched ssa.  */
     650         1114 :                   tree prev = *SSAex.get ({e->src, bucket});
     651         1114 :                   gcc_assert (prev);
     652         1114 :                   if (constant_p (prev))
     653              :                     {
     654          284 :                       const uint64_t x = tree_to_uhwi (prev);
     655          284 :                       tree cst = build_int_cst (gcov_type_node, x & *bitmask);
     656          284 :                       if (phi)
     657          284 :                         add_phi_arg (phi, cst, e, UNKNOWN_LOCATION);
     658              :                       else
     659            0 :                         ssa = cst;
     660              :                     }
     661              :                   else
     662              :                     {
     663          830 :                       tree lhs = make_ssa_name (gcov_type_node);
     664          830 :                       tree mask = build_int_cst (gcov_type_node, *bitmask);
     665          830 :                       gassign *put = gimple_build_assign (lhs, BIT_AND_EXPR,
     666              :                                                           prev, mask);
     667          830 :                       gsi_insert_on_edge (e, put);
     668          830 :                       if (phi)
     669          830 :                         add_phi_arg (phi, lhs, e, UNKNOWN_LOCATION);
     670              :                       else
     671            0 :                         ssa = lhs;
     672              :                     }
     673              :                 }
     674              :               else
     675              :                 {
     676              :                   /* There is a phi and this edge is a back edge,
     677              :                      which means the predecessor (and descendant) exit
     678              :                      SSA has not been created yet.  */
     679           63 :                   gcc_assert (phi);
     680           63 :                   gcc_assert (e->flags & EDGE_DFS_BACK);
     681           63 :                   fixup todo = fixup_and (phi, e, bucket, *bitmask,
     682              :                                           gcov_type_node);
     683           63 :                   todos.safe_push (todo);
     684              :                 }
     685              :             }
     686              : 
     687              :           /* Bitwise IOR.  Unlike the AND this assignment can always be created
     688              :              right away as this should be applied to the result of the phi,
     689              :              AND, or single predecessor's exit SSA, and all of those have
     690              :              already been created.  */
     691         2768 :           const uint64_t *ior = iors.get ({bb, bucket});
     692         2768 :           if (ior && !ssa)
     693              :             {
     694              :               /* In case there was no predecessor, the IOR/initial state can
     695              :                  just be a constant.  In this case, the IOR also becomes the
     696              :                  block's entry node which means it will be considered for
     697              :                  flushing in single-vertex paths.  */
     698          289 :               ssa = build_int_cst (gcov_type_node, *ior);
     699          289 :               SSAen.put ({bb, bucket}, ssa);
     700              :             }
     701          173 :           else if (ior && all_bits_set_p (*ior, bucketsize))
     702            0 :             ssa = build_all_ones_cst (gcov_type_node);
     703         2479 :           else if (ior)
     704              :             {
     705          173 :               gcc_assert (ssa);
     706          173 :               tree mask = build_int_cst (gcov_type_node, *ior);
     707          173 :               ssa = safe_insert_ior (bb, ssa, mask, phi, gcov_type_node);
     708              :             }
     709              : 
     710         2768 :           if (ssa)
     711         2075 :             SSAex.put ({bb, bucket}, ssa);
     712              :         }
     713              :     }
     714              : 
     715              :   /* Apply fixups -- now that all exit SSA names are created we can properly
     716              :      set the phi argument if there is a phi node, and emit the (x & mask)
     717              :      instruction if necessary.  */
     718          918 :   for (fixup &todo : todos)
     719              :     {
     720           63 :       tree *exit = SSAex.get ({todo.e->src, todo.bucket});
     721           63 :       gcc_assert (exit && *exit);
     722           63 :       gcc_checking_assert (todo.phi);
     723           63 :       if (todo.mask)
     724              :         {
     725           63 :           gassign *put = gimple_build_assign (todo.lhs, BIT_AND_EXPR, *exit,
     726              :                                               todo.mask);
     727           63 :           gsi_insert_on_edge (todo.e, put);
     728              :         }
     729              : 
     730           63 :       add_phi_arg (todo.phi, todo.lhs ? todo.lhs : *exit, todo.e,
     731              :                    UNKNOWN_LOCATION);
     732              :     }
     733              : 
     734          285 :   tree atomic_ior = nullptr;
     735          285 :   if (flag_profile_update == PROFILE_UPDATE_ATOMIC)
     736           34 :     atomic_ior = builtin_decl_explicit
     737           34 :       (TYPE_PRECISION (gcov_type_node) > 32
     738              :        ? BUILT_IN_ATOMIC_FETCH_OR_8
     739              :        : BUILT_IN_ATOMIC_FETCH_OR_4);
     740              : 
     741              :   /* Finally, add instructions to update the global counters.  */
     742         2610 :   for (basic_block bb : blocks)
     743              :     {
     744         2040 :       gimple_stmt_iterator gsi = gsi_after_labels (bb);
     745         4808 :       for (size_t bucket = 0; bucket != nbuckets; ++bucket)
     746              :         {
     747         2768 :           const uint64_t *bitmask = flushes.get ({bb, bucket});
     748         2768 :           if (!bitmask || !*bitmask)
     749         2261 :             continue;
     750              : 
     751          507 :           tree *counter = SSAen.get ({bb, bucket});
     752          507 :           gcc_checking_assert (counter);
     753          507 :           if (!*counter)
     754            0 :             continue;
     755              : 
     756          507 :           tree mask = nullptr;
     757          507 :           if (!all_bits_set_p (*bitmask, bucketsize))
     758          497 :             mask = build_int_cst (gcov_type_node, *bitmask);
     759              : 
     760          507 :           if (bb_has_abnormal_pred (bb))
     761           18 :             flush_on_edges (bb, bucket, *counter, mask, atomic_ior,
     762              :                             gcov_type_node);
     763              :           else
     764          489 :             flush_on_gsi (&gsi, bucket, *counter, mask, atomic_ior,
     765              :                           gcov_type_node);
     766              :         }
     767              :     }
     768              : 
     769          285 :   const unsigned npaths = paths.length ();
     770          285 :   blocks.release ();
     771          285 :   release_vec_vec (paths);
     772          285 :   return npaths;
     773          285 : }
        

Generated by: LCOV version 2.4-beta

LCOV profile is generated on x86_64 machine using following configure options: configure --disable-bootstrap --enable-coverage=opt --enable-languages=c,c++,fortran,go,jit,lto,rust,m2 --enable-host-shared. GCC test suite is run with the built compiler.