This RFC proposes boolean flags support for arcs coverage. The current implementation of branch and line coverage in gcov uses 8-byte cumulative counters that are updated via an increment operation. This leads to a significant slowdown of the instrumented program. The slowdown is most pronounced in atomic-update mode: on some 32-bit platforms that do not support 64-bit atomic operations, an instrumented program may be slowed down by more than 20 times due to two atomic increments per counter.
However, in a common use case we do not need the execution count of every branch or line to measure coverage; we only need to know whether a given structural code element was executed. Therefore, we propose special "boolean flags" that have only two states: set or unset (0/1). These flags reduce the memory consumed by boolean flags as well as the size of the instrumentation code that updates them. They also improve the performance of the instrumented program: the overhead of atomically updating these flags is reduced to less than 2 times. We introduce a new -fbinary-coverage option to enable boolean flags. This option allows using a new type of coverage counters that are single-byte in size. They are updated via a single plain 8-bit store operation in default mode (-fprofile-update=single) and a single relaxed atomic 8-bit store in thread-safe mode (-fprofile-update=atomic). This option also enables -fprofile-arcs to reuse the existing infrastructure for default cumulative counters, and -ftest-coverage to generate .gcno files. The corresponding counter records in these files are also single-byte in size. LLVM 19 introduced a similar option -enable-single-byte-coverage [1], which enables boolean flags for line coverage. Recently, a patchset enabling branch coverage was added [2]. The results of adding boolean flags have been positive. It would be great to get community feedback on the following points: 1. Is there a general consensus to add this functionality to GCC? 2. Is the general implementation strategy followed by this patch acceptable? 3. Is the terminology introduced by this patch acceptable? Note, this is only a prototype implementation, and some parts have been simplified, for example, the algorithm for selecting which arcs to instrument (select_arcs_binary_coverage function), and the algorithm for resolving CFG coverage from the instrumented arcs (solve_flow_graph_bin function). [1]: https://discourse.llvm.org/t/rfc-single-byte-counters-for-source-based-code-coverage/75685 [2]: https://discourse.llvm.org/t/rfc-integrating-singlebytecoverage-with-branch-coverage/82492 gcc/ChangeLog: * common.opt: Add new option fbinary-coverage. * coverage.cc (get_gcov_type): New overload with a counter argument. (read_counts_file): Add support for reading binary counters. (get_coverage_counts): Likewise. (coverage_counter_alloc): Use the new overload of get_gcov_type function. (tree_coverage_counter_ref): Likewise. (tree_coverage_counter_addr): Likewise. (coverage_end_function): Likewise. (build_fn_info_type): Change a type of the ctr_info::values field to ptr_type_node to support counters of different sizes. * gcc.cc: Add support of fbinary-coverage option. * gcov-counter.def (GCOV_BIN_COUNTER_ARCS): New. * gcov-dump.cc (tag_counters): Add support for reading binary counters. * gcov-io.cc (gcov_read_bin_counter): New. * gcov-io.h (GCOV_TAG_BIN_COUNTER_LENGTH): New. (GCOV_TAG_BIN_COUNTER_NUM): New. (gcov_read_bin_counter): New. * gcov.cc (block_info::block_info): Add initialization of new flags: fake_preds and fake_succs. (solve_flow_graph_bin): New function for solving the flow graph for binary counters. (process_all_functions): Call solve_flow_graph_bin. (read_count_file): Add support for reading binary counters. (add_line_counts): Determine line counts when using binary counters. (accumulate_line_info): Likewise. (output_branch_count): Output the coverage status of branches instead of execution counts when using binary counters. * opts.cc (finish_options): Disallow -fbinary-coverage and -fbranch-probabilities to be specified together. * profile.cc (select_arcs_binary_coverage): New. (branch_prob): Call select_arcs_binary_coverage. * tree-profile.cc (gen_counter_update): Add a new parameter, add support for binary counters. (gimple_gen_edge_profiler): Call updated gen_counter_update. (gimple_gen_time_profiler): Likewise. (tree_profiling): Set bin_counter_update for indicating the binary counters update method. libgcc/ChangeLog: * Makefile.in: Add _gcov_merge_bin. * libgcov-driver.c (are_all_counters_zero): Add new parameter, add support for binary counters. (buffer_fn_data): Add support for reading binary counters. (merge_one_data): Add support for binary counters. (dump_bin_counter): New. (write_one_data): Add support for dumping binary counters. (gcov_do_dump): Skip computing run_max when default arcs counters are not used. * libgcov-interface.c (gcov_clear): Add support for binary counters. * libgcov-merge.c (__gcov_merge_bin): New. * libgcov-util.c (set_fn_ctrs): Add support for binary counters. (tag_counters): Likewise. (gcov_read_bin_counter_mem): New. (merge_wrapper): Use the new type of gcov_value_buf. (__gcov_bin_counter_op): New. (gcov_profile_normalize): Skip computing curr_max_val when default arcs counters are not used. (compute_one_gcov): Skip computing the result when default arcs counters are not used. (gcov_info_count_all_cold): Likewise. * libgcov.h (gcov_read_bin_counter): New. (L_gcov_merge_bin): New. (gcov_read_bin_counter_mem): New. (struct gcov_ctr_info): Change type of the values field to union to support pointers to counters of different sizes. (__gcov_merge_bin): New. (gcov_get_counter_binary): New. gcc/testsuite/ChangeLog: * lib/gcov.exp: Add checks for binary counters. * g++.dg/gcov/gcov-11b.C: New test. * g++.dg/gcov/gcov-15b.C: New test. * g++.dg/gcov/gcov-1b.C: New test. * gcc.misc-tests/gcov-35.c: New test. * gcc.misc-tests/gcov-7b.c: New test. --- gcc/common.opt | 8 +- gcc/coverage.cc | 34 ++- gcc/coverage.h | 1 + gcc/gcc.cc | 5 +- gcc/gcov-counter.def | 3 + gcc/gcov-dump.cc | 12 +- gcc/gcov-io.cc | 15 ++ gcc/gcov-io.h | 3 + gcc/gcov.cc | 291 +++++++++++++++++++++-- gcc/opts.cc | 4 + gcc/profile.cc | 42 +++- gcc/tree-profile.cc | 63 +++-- libgcc/Makefile.in | 3 +- libgcc/libgcov-driver.c | 106 ++++++--- libgcc/libgcov-interface.c | 9 +- libgcc/libgcov-merge.c | 20 ++ libgcc/libgcov-util.c | 87 +++++-- libgcc/libgcov.h | 30 ++- gcc/testsuite/g++.dg/gcov/gcov-11b.C | 43 ++++ gcc/testsuite/g++.dg/gcov/gcov-15b.C | 26 ++ gcc/testsuite/g++.dg/gcov/gcov-1b.C | 314 +++++++++++++++++++++++++ gcc/testsuite/gcc.misc-tests/gcov-35.c | 303 ++++++++++++++++++++++++ gcc/testsuite/gcc.misc-tests/gcov-7b.c | 87 +++++++ gcc/testsuite/lib/gcov.exp | 50 ++++ 24 files changed, 1456 insertions(+), 103 deletions(-) create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-11b.C create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-15b.C create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-1b.C create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-35.c create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-7b.c diff --git a/gcc/common.opt b/gcc/common.opt index 218dddf5dfe..261132bbf57 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -2559,6 +2559,10 @@ fpath-coverage Common Var(path_coverage_flag) Insert path profiling code. +fbinary-coverage +Common Var(binary_coverage_flag) +Insert binary arc-based coverage profiling code. + fpartial-inlining Common Var(flag_partial_inlining) Optimization Perform partial inlining. @@ -2640,7 +2644,7 @@ Common Var(profile_abs_path_flag) Generate absolute source path names for gcov. fprofile-arcs -Common Var(profile_arc_flag) +Common Var(profile_arc_flag) EnabledBy(fbinary-coverage) Insert arc-based program profiling code. fcondition-coverage @@ -3149,7 +3153,7 @@ Common Var(flag_syntax_only) Check for syntax errors, then stop. ftest-coverage -Common Var(flag_test_coverage) EnabledBy(fpath-coverage || fcondition-coverage) +Common Var(flag_test_coverage) EnabledBy(fpath-coverage || fcondition-coverage || fbinary-coverage) Create data files needed by \"gcov\". fthread-jumps diff --git a/gcc/coverage.cc b/gcc/coverage.cc index cbe58033c2d..84d7665de8c 100644 --- a/gcc/coverage.cc +++ b/gcc/coverage.cc @@ -143,6 +143,16 @@ get_gcov_type (void) return lang_hooks.types.type_for_mode (mode, false); } +tree +get_gcov_type (unsigned counter) +{ + scalar_int_mode mode + = smallest_int_mode_for_size (counter == GCOV_BIN_COUNTER_ARCS ? 8 + : LONG_LONG_TYPE_SIZE > 32 ? 64 : 32) + .require (); + return lang_hooks.types.type_for_mode (mode, false); +} + /* Return the type node for gcov_unsigned_t. */ static tree @@ -214,7 +224,7 @@ read_counts_file (void) /* Read checksum. */ gcov_read_unsigned (); - counts_hash = new hash_table<counts_entry> (10); + counts_hash = new hash_table<counts_entry> (GCOV_COUNTERS); while ((tag = gcov_read_unsigned ())) { gcov_unsigned_t length; @@ -245,12 +255,15 @@ read_counts_file (void) counts_entry **slot, *entry, elt; int read_length = (int)length; length = read_length > 0 ? read_length : 0; - unsigned n_counts = GCOV_TAG_COUNTER_NUM (abs (read_length)); + unsigned n_counts; unsigned ix; elt.ident = fn_ident; elt.ctr = GCOV_COUNTER_FOR_TAG (tag); + n_counts = (elt.ctr == GCOV_BIN_COUNTER_ARCS + ? GCOV_TAG_BIN_COUNTER_NUM (abs (read_length)) + : GCOV_TAG_COUNTER_NUM (abs (read_length))); slot = counts_hash->find_slot (&elt, INSERT); entry = *slot; if (!entry) @@ -276,7 +289,9 @@ read_counts_file (void) } if (read_length > 0) for (ix = 0; ix != n_counts; ix++) - entry->counts[ix] = gcov_read_counter (); + entry->counts[ix] = (entry->ctr == GCOV_BIN_COUNTER_ARCS + ? (gcov_type) gcov_read_bin_counter () + : gcov_read_counter ()); } gcov_sync (offset, length); if ((is_error = gcov_is_error ())) @@ -336,7 +351,7 @@ get_coverage_counts (unsigned counter, unsigned cfg_checksum, entry = counts_hash->find (&elt); if (!entry) { - if (counter == GCOV_COUNTER_ARCS) + if (counter == GCOV_COUNTER_ARCS || counter == GCOV_BIN_COUNTER_ARCS) warning_at (DECL_SOURCE_LOCATION (current_function_decl), OPT_Wmissing_profile, "profile for function %qD not found in profile data", @@ -423,7 +438,7 @@ coverage_counter_alloc (unsigned counter, unsigned num) if (!fn_v_ctrs[counter]) { - tree array_type = build_array_type (get_gcov_type (), NULL_TREE); + tree array_type = build_array_type (get_gcov_type (counter), NULL_TREE); fn_v_ctrs[counter] = build_var (current_function_decl, array_type, counter); @@ -441,7 +456,7 @@ coverage_counter_alloc (unsigned counter, unsigned num) tree tree_coverage_counter_ref (unsigned counter, unsigned no) { - tree gcov_type_node = get_gcov_type (); + tree gcov_type_node = get_gcov_type (counter); gcc_assert (no < fn_n_ctrs[counter] - fn_b_ctrs[counter]); @@ -457,7 +472,7 @@ tree_coverage_counter_ref (unsigned counter, unsigned no) tree tree_coverage_counter_addr (unsigned counter, unsigned no) { - tree gcov_type_node = get_gcov_type (); + tree gcov_type_node = get_gcov_type (counter); gcc_assert (no < fn_n_ctrs[counter] - fn_b_ctrs[counter]); no += fn_b_ctrs[counter]; @@ -723,7 +738,7 @@ coverage_end_function (unsigned lineno_checksum, unsigned cfg_checksum) if (var) { tree array_type = build_index_type (size_int (fn_n_ctrs[i] - 1)); - array_type = build_array_type (get_gcov_type (), array_type); + array_type = build_array_type (get_gcov_type (i), array_type); TREE_TYPE (var) = array_type; DECL_SIZE (var) = TYPE_SIZE (array_type); DECL_SIZE_UNIT (var) = TYPE_SIZE_UNIT (array_type); @@ -798,8 +813,7 @@ build_fn_info_type (tree type, unsigned counters, tree gcov_info_type) fields = field; /* ctr_info::values */ - field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE, - build_pointer_type (get_gcov_type ())); + field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE, ptr_type_node); DECL_CHAIN (field) = fields; fields = field; diff --git a/gcc/coverage.h b/gcc/coverage.h index 4450f427e98..6c4b0576569 100644 --- a/gcc/coverage.h +++ b/gcc/coverage.h @@ -58,6 +58,7 @@ extern gcov_type *get_coverage_counts (unsigned /*counter*/, unsigned /*n_counts*/); extern tree get_gcov_type (void); +extern tree get_gcov_type (unsigned /*counter*/); extern bool coverage_node_map_initialized_p (void); #endif diff --git a/gcc/gcc.cc b/gcc/gcc.cc index b8b0db4ed5e..332365bab69 100644 --- a/gcc/gcc.cc +++ b/gcc/gcc.cc @@ -1204,7 +1204,8 @@ proper position among the other output files. */ %:include(libgomp.spec)%(link_gomp)}\ %{fgnu-tm:%:include(libitm.spec)%(link_itm)}\ " STACK_SPLIT_SPEC "\ - %{fprofile-arcs|fcondition-coverage|fpath-coverage|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \ + %{fprofile-arcs|fcondition-coverage|fpath-coverage|fbinary-coverage|fprofile-generate*|coverage:-lgcov}\ + " SANITIZER_SPEC "\ %{!nostdlib:%{!r:%{!nodefaultlibs:%(link_ssp) %(link_gcc_c_sequence)}}}\ %{!nostdlib:%{!r:%{!nostartfiles:%E}}} %{T*} \n%(post_link) }}}}}}" #endif @@ -1327,7 +1328,7 @@ static const char *cc1_options = %{!fsyntax-only:%{S:%W{o*}%{!o*:-o %w%b.s}}}\ %{fsyntax-only:-o %j} %{-param*}\ %{coverage:-fprofile-arcs -ftest-coverage}\ - %{fprofile-arcs|fcondition-coverage|fpath-coverage|fprofile-generate*|coverage:\ + %{fprofile-arcs|fcondition-coverage|fpath-coverage|fbinary-coverage|fprofile-generate*|coverage:\ %{!fprofile-update=single:\ %{pthread:-fprofile-update=prefer-atomic}}}"; diff --git a/gcc/gcov-counter.def b/gcc/gcov-counter.def index 984e08ff6b9..41a55972ad9 100644 --- a/gcc/gcov-counter.def +++ b/gcc/gcov-counter.def @@ -55,3 +55,6 @@ DEF_GCOV_COUNTER(GCOV_COUNTER_CONDS, "conditions", _ior) /* Paths. The counter is interpreted as a bit-set. */ DEF_GCOV_COUNTER(GCOV_COUNTER_PATHS, "ior", _ior) + +/* Boolean arc transitions. */ +DEF_GCOV_COUNTER(GCOV_BIN_COUNTER_ARCS, "bin_arcs", _bin) diff --git a/gcc/gcov-dump.cc b/gcc/gcov-dump.cc index 0f9992c60f1..cffb875f43e 100644 --- a/gcc/gcov-dump.cc +++ b/gcc/gcov-dump.cc @@ -483,10 +483,12 @@ tag_counters (const char *filename ATTRIBUTE_UNUSED, #include "gcov-counter.def" }; #undef DEF_GCOV_COUNTER - int n_counts = GCOV_TAG_COUNTER_NUM (length); + unsigned counter_idx = GCOV_COUNTER_FOR_TAG (tag); + int n_counts = (counter_idx == GCOV_BIN_COUNTER_ARCS + ? GCOV_TAG_BIN_COUNTER_NUM (length) + : GCOV_TAG_COUNTER_NUM (length)); bool has_zeros = n_counts < 0; n_counts = abs (n_counts); - unsigned counter_idx = GCOV_COUNTER_FOR_TAG (tag); printf (" %s %u counts%s", counter_names[counter_idx], n_counts, @@ -495,7 +497,11 @@ tag_counters (const char *filename ATTRIBUTE_UNUSED, { vector<gcov_type> counters; for (int ix = 0; ix != n_counts; ix++) - counters.push_back (has_zeros ? 0 : gcov_read_counter ()); + if (counter_idx == GCOV_BIN_COUNTER_ARCS) + counters.push_back (has_zeros ? 0 + : (gcov_type) gcov_read_bin_counter ()); + else + counters.push_back (has_zeros ? 0 : gcov_read_counter ()); /* Make stable sort for TOP N counters. */ if (flag_dump_stable) diff --git a/gcc/gcov-io.cc b/gcc/gcov-io.cc index 0fda8795b21..ce05bb6d9bd 100644 --- a/gcc/gcov-io.cc +++ b/gcc/gcov-io.cc @@ -456,6 +456,21 @@ gcov_read_counter (void) return value; } +/* Read binary counter value from a coverage file. Sets error flag on + file error, overflow flag on overflow. */ + +GCOV_LINKAGE char +gcov_read_bin_counter (void) +{ + char value; + void *buffer = gcov_read_bytes (&value, 1); + + if (!buffer) + return 0; + + return value; +} + /* Mangle filename path of BASE and output new allocated pointer with mangled path. */ diff --git a/gcc/gcov-io.h b/gcc/gcov-io.h index 5a0b109c49b..c48c7232231 100644 --- a/gcc/gcov-io.h +++ b/gcc/gcov-io.h @@ -271,6 +271,8 @@ typedef uint64_t gcov_type_unsigned; #define GCOV_TAG_COUNTER_BASE ((gcov_unsigned_t)0x01a10000) #define GCOV_TAG_COUNTER_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE) #define GCOV_TAG_COUNTER_NUM(LENGTH) ((LENGTH / GCOV_WORD_SIZE) / 2) +#define GCOV_TAG_BIN_COUNTER_LENGTH(NUM) (NUM) +#define GCOV_TAG_BIN_COUNTER_NUM(LENGTH) (LENGTH) #define GCOV_TAG_OBJECT_SUMMARY ((gcov_unsigned_t)0xa1000000) #define GCOV_TAG_OBJECT_SUMMARY_LENGTH (2 * GCOV_WORD_SIZE) #define GCOV_TAG_PROGRAM_SUMMARY ((gcov_unsigned_t)0xa3000000) /* Obsolete */ @@ -379,6 +381,7 @@ GCOV_LINKAGE int gcov_open (const char *, int) ATTRIBUTE_HIDDEN; GCOV_LINKAGE int gcov_close (void) ATTRIBUTE_HIDDEN; GCOV_LINKAGE gcov_unsigned_t gcov_read_unsigned (void) ATTRIBUTE_HIDDEN; GCOV_LINKAGE gcov_type gcov_read_counter (void) ATTRIBUTE_HIDDEN; +GCOV_LINKAGE char gcov_read_bin_counter (void) ATTRIBUTE_HIDDEN; GCOV_LINKAGE void gcov_read_summary (struct gcov_summary *) ATTRIBUTE_HIDDEN; GCOV_LINKAGE const char *gcov_read_string (void); GCOV_LINKAGE void gcov_sync (gcov_position_t /*base*/, diff --git a/gcc/gcov.cc b/gcc/gcov.cc index 9797ddd8ca8..442dc4279c9 100644 --- a/gcc/gcov.cc +++ b/gcc/gcov.cc @@ -236,6 +236,8 @@ public: unsigned valid_chain : 1; unsigned invalid_chain : 1; unsigned exceptional : 1; + unsigned fake_preds : 1; + unsigned fake_succs : 1; /* Block is a call instrumenting site. */ unsigned is_call_site : 1; /* Does the call. */ @@ -265,8 +267,8 @@ public: block_info::block_info (): succ (NULL), pred (NULL), num_succ (0), num_pred (0), id (0), count (0), count_valid (0), valid_chain (0), invalid_chain (0), - exceptional (0), is_call_site (0), is_call_return (0), is_nonlocal_return (0), - locations (), chain (NULL) + exceptional (0), fake_preds (0), fake_succs (0), is_call_site (0), + is_call_return (0), is_nonlocal_return (0), locations (), chain (NULL) { cycle.arc = NULL; } @@ -636,6 +638,9 @@ static char *da_file_name; static int no_data_file; +/* Indicate whether the arcs counters are binary. */ +static bool use_bin_arcs = false; + /* If there is several input files, compute and display results after reading all data files. This way if two or more gcda file refer to the same source file (eg inline subprograms in a .h file), the @@ -809,6 +814,7 @@ static void read_graph_file (void); static int read_count_file (void); static void solve_flow_graph (function_info *); static void find_prime_paths (function_info *fn); +static void solve_flow_graph_bin (function_info *); static void find_exception_blocks (function_info *); static void add_branch_counts (coverage_info *, const arc_info *); static void add_condition_counts (coverage_info *, const block_info *); @@ -1821,7 +1827,11 @@ process_all_functions (void) if (fn->is_group) fn->lines.resize (fn->end_line - fn->start_line + 1); - solve_flow_graph (fn); + if (use_bin_arcs) + solve_flow_graph_bin (fn); + else + solve_flow_graph (fn); + if (fn->has_catch) find_exception_blocks (fn); @@ -2557,10 +2567,23 @@ read_count_file (void) } } } + else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_BIN_COUNTER_ARCS) && fn) + { + length = abs (read_length); + if (length != GCOV_TAG_BIN_COUNTER_LENGTH (fn->counts.size ())) + goto mismatch; + if (!use_bin_arcs) + use_bin_arcs = true; + + if (read_length > 0) + for (ix = 0; ix != fn->counts.size (); ix++) + fn->counts[ix] |= gcov_read_bin_counter (); + } else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn) { length = abs (read_length); - if (length != GCOV_TAG_COUNTER_LENGTH (fn->counts.size ())) + if (use_bin_arcs + || length != GCOV_TAG_COUNTER_LENGTH (fn->counts.size ())) goto mismatch; if (read_length > 0) @@ -2861,6 +2884,199 @@ solve_flow_graph (function_info *fn) } } +/* Solve the flow graph in the case of using binary counters. Propagate + binary counts from the instrumented arcs to the blocks and the uninstrumented + arcs. */ + +static void +solve_flow_graph_bin (function_info *fn) +{ + unsigned ix; + arc_info *arc; + gcov_type *count_ptr = &fn->counts.front (); + block_info *blk; + + /* The arcs were built in reverse order. Fix that now. */ + for (ix = fn->blocks.size (); ix--;) + { + arc_info *arc_p, *arc_n; + + for (arc_p = NULL, arc = fn->blocks[ix].succ; arc; + arc_p = arc, arc = arc_n) + { + arc_n = arc->succ_next; + arc->succ_next = arc_p; + } + fn->blocks[ix].succ = arc_p; + + for (arc_p = NULL, arc = fn->blocks[ix].pred; arc; + arc_p = arc, arc = arc_n) + { + arc_n = arc->pred_next; + arc->pred_next = arc_p; + } + fn->blocks[ix].pred = arc_p; + } + + if (fn->blocks.size () < 2) + fnotice (stderr, "%s:'%s' lacks entry and/or exit blocks\n", bbg_file_name, + fn->get_name ()); + else + { + if (fn->blocks[ENTRY_BLOCK].num_pred) + fnotice (stderr, "%s:'%s' has arcs to entry block\n", bbg_file_name, + fn->get_name ()); + else + /* We can't deduce the entry block counts from the lack of + predecessors. */ + fn->blocks[ENTRY_BLOCK].num_pred = ~(unsigned) 0; + + if (fn->blocks[EXIT_BLOCK].num_succ) + fnotice (stderr, "%s:'%s' has arcs from exit block\n", bbg_file_name, + fn->get_name ()); + else + /* Likewise, we can't deduce exit block counts from the lack + of its successors. */ + fn->blocks[EXIT_BLOCK].num_succ = ~(unsigned) 0; + } + + /* Propagate the measured counts, this must be done in the same + order as the code in profile.cc. */ + for (unsigned i = 0; i < fn->blocks.size (); i++) + { + blk = &fn->blocks[i]; + block_info const *prev_dst = NULL; + int out_of_order = 0; + int non_fake_succ = 0; + + for (arc = blk->succ; arc; arc = arc->succ_next) + { + if (!arc->fake) + non_fake_succ++; + else + { + if (blk->fake_succs == 0) + blk->fake_succs = 1; + arc->dst->fake_preds = 1; + } + + if (arc->fake || !arc->on_tree) + { + if (!arc->fake && count_ptr) + arc->count = *count_ptr++; + arc->count_valid = 1; + blk->num_succ--; + arc->dst->num_pred--; + if (arc->count) + { + if (!blk->count) + blk->count = 1; + if (!arc->dst->count) + arc->dst->count = 1; + } + } + if (prev_dst && prev_dst > arc->dst) + out_of_order = 1; + prev_dst = arc->dst; + } + if (non_fake_succ == 1) + { + /* If there is only one non-fake exit, it is an + unconditional branch. */ + for (arc = blk->succ; arc; arc = arc->succ_next) + if (!arc->fake) + { + arc->is_unconditional = 1; + /* If this block is instrumenting a call, it might be + an artificial block. It is not artificial if it has + a non-fallthrough exit, or the destination of this + arc has more than one entry. Mark the destination + block as a return site, if none of those conditions + hold. */ + if (blk->is_call_site && arc->fall_through + && arc->dst->pred == arc && !arc->pred_next) + arc->dst->is_call_return = 1; + } + } + + /* Sort the successor arcs into ascending dst order. profile.cc + normally produces arcs in the right order, but sometimes with + one or two out of order. We're not using a particularly + smart sort. */ + if (out_of_order) + { + arc_info *start = blk->succ; + unsigned changes = 1; + + while (changes) + { + arc_info *arc, *arc_p, *arc_n; + + changes = 0; + for (arc_p = NULL, arc = start; (arc_n = arc->succ_next);) + { + if (arc->dst > arc_n->dst) + { + changes = 1; + if (arc_p) + arc_p->succ_next = arc_n; + else + start = arc_n; + arc->succ_next = arc_n->succ_next; + arc_n->succ_next = arc; + arc_p = arc_n; + } + else + { + arc_p = arc; + arc = arc_n; + } + } + } + blk->succ = start; + } + } + + for (arc = fn->blocks[EXIT_BLOCK].pred; arc; arc = arc->pred_next) + { + block_info *blk = arc->src; + if (!arc->count_valid) + { + unsigned total = 0; + for (arc_info *arc_n = blk->succ; arc_n; arc_n = arc_n->succ_next) + if (arc_n->count_valid && arc_n->count) + { + total |= arc_n->count; + break; + } + arc->count = blk->count && !total; + arc->count_valid = 1; + blk->num_succ--; + arc->dst->num_pred--; + if (arc->count && !arc->dst->count) + arc->dst->count = 1; + } + } + for (unsigned i = 0; i < fn->blocks.size (); i++) + { + blk = &fn->blocks[i]; + if (!blk->num_succ || !blk->num_pred) + blk->count_valid = 1; + } + + /* If the graph has been correctly solved, every block will have a + valid count. */ + for (unsigned i = 0; i < fn->blocks.size (); i++) + { + if (!fn->blocks[i].count_valid) + { + fnotice (stderr, "%s:graph is unsolvable for '%s'\n", bbg_file_name, + fn->get_name ()); + break; + } + } +} + /* Find the prime paths of the function from the CFG and add to FN using the same function as gcc. It relies on gcc recording the CFG faithfully. Storing the paths explicitly takes up way too much @@ -3332,7 +3548,10 @@ add_line_counts (coverage_info *coverage, function_info *fn) if (block->count == 0) line->has_unexecuted_block = 1; } - line->count += block->count; + if (use_bin_arcs) + line->count |= block->count; + else + line->count += block->count; } else { @@ -3352,7 +3571,10 @@ add_line_counts (coverage_info *coverage, function_info *fn) if (block->count == 0) line->has_unexecuted_block = 1; } - line->count += block->count; + if (use_bin_arcs) + line->count |= block->count; + else + line->count += block->count; } } @@ -3413,13 +3635,21 @@ static void accumulate_line_info (line_info *line, source_info *src, { for (arc_info *arc = (*it)->pred; arc; arc = arc->pred_next) if (!line->has_block (arc->src)) - count += arc->count; - for (arc_info *arc = (*it)->succ; arc; arc = arc->succ_next) - arc->cs_count = arc->count; + { + if (use_bin_arcs) + count |= arc->count; + else + count += arc->count; + } + if (!use_bin_arcs) + for (arc_info *arc = (*it)->succ; arc; arc = arc->succ_next) + arc->cs_count = arc->count; } - /* Now, add the count of loops entirely on this line. */ - count += get_cycles_count (*line); + if (!use_bin_arcs) + /* Now, add the count of loops entirely on this line. */ + count += get_cycles_count (*line); + line->count = count; if (line->count > src->maximum_count) @@ -3539,9 +3769,14 @@ output_branch_count (FILE *gcov_file, int ix, const arc_info *arc) { if (arc->src->count) { - fnotice (gcov_file, "call %2d returned %s\n", ix, - format_gcov (arc->src->count - arc->count, - arc->src->count, -flag_counts)); + if (use_bin_arcs) + fnotice (gcov_file, "call %2d%s\n", ix, + arc->src->count - arc->count + ? " returned" : " never returned"); + else + fnotice (gcov_file, "call %2d returned %s\n", ix, + format_gcov (arc->src->count - arc->count, arc->src->count, + -flag_counts)); } else fnotice (gcov_file, "call %2d never executed\n", ix); @@ -3549,10 +3784,20 @@ output_branch_count (FILE *gcov_file, int ix, const arc_info *arc) else if (!arc->is_unconditional) { if (arc->src->count) - fnotice (gcov_file, "branch %2d taken %s%s", ix, - format_gcov (arc->count, arc->src->count, -flag_counts), - arc->fall_through ? " (fallthrough)" - : arc->is_throw ? " (throw)" : ""); + { + if (use_bin_arcs) + fnotice (gcov_file, "branch %2d%s%s", ix, + arc->count ? " taken" : " not taken", + arc->fall_through ? " (fallthrough)" + : arc->is_throw ? " (throw)" + : ""); + else + fnotice (gcov_file, "branch %2d taken %s%s", ix, + format_gcov (arc->count, arc->src->count, -flag_counts), + arc->fall_through ? " (fallthrough)" + : arc->is_throw ? " (throw)" + : ""); + } else fnotice (gcov_file, "branch %2d never executed%s", ix, (arc->fall_through ? " (fallthrough)" @@ -3566,8 +3811,14 @@ output_branch_count (FILE *gcov_file, int ix, const arc_info *arc) else if (flag_unconditional && !arc->dst->is_call_return) { if (arc->src->count) - fnotice (gcov_file, "unconditional %2d taken %s\n", ix, - format_gcov (arc->count, arc->src->count, -flag_counts)); + { + if (use_bin_arcs) + fnotice (gcov_file, "unconditional %2d%s\n", ix, + arc->count ? " taken" : " not taken"); + else + fnotice (gcov_file, "unconditional %2d taken %s\n", ix, + format_gcov (arc->count, arc->src->count, -flag_counts)); + } else fnotice (gcov_file, "unconditional %2d never executed\n", ix); } diff --git a/gcc/opts.cc b/gcc/opts.cc index 342517528e8..f7ff4d62373 100644 --- a/gcc/opts.cc +++ b/gcc/opts.cc @@ -1529,6 +1529,10 @@ finish_options (struct gcc_options *opts, struct gcc_options *opts_set, opts->x_profile_flag = 0; } + if (opts->x_binary_coverage_flag && opts->x_flag_branch_probabilities) + sorry ("%<-fbinary-coverage%> is not supported with " + "%<-fbranch-probabilities%>"); + if (opts->x_warn_strict_flex_arrays) if (opts->x_flag_strict_flex_arrays == 0) { diff --git a/gcc/profile.cc b/gcc/profile.cc index 17841fc723a..e9da2d00742 100644 --- a/gcc/profile.cc +++ b/gcc/profile.cc @@ -137,6 +137,7 @@ static vec <afdo_fdo_record> afdo_fdo_records; /* Forward declarations. */ static void find_spanning_tree (struct edge_list *); +static void select_arcs_binary_coverage (struct edge_list *); /* Add edge instrumentation code to the entire insn chain. @@ -1411,7 +1412,12 @@ branch_prob (bool thunk) as possible to minimize number of edge splits necessary. */ if (!thunk) - find_spanning_tree (el); + { + if (binary_coverage_flag) + select_arcs_binary_coverage (el); + else + find_spanning_tree (el); + } else { edge e; @@ -1649,7 +1655,9 @@ branch_prob (bool thunk) /* For each edge not on the spanning tree, add counting code. */ if (profile_arc_flag - && coverage_counter_alloc (GCOV_COUNTER_ARCS, num_instrumented)) + && coverage_counter_alloc (binary_coverage_flag ? GCOV_BIN_COUNTER_ARCS + : GCOV_COUNTER_ARCS, + num_instrumented)) { unsigned n_instrumented; @@ -1797,6 +1805,36 @@ find_spanning_tree (struct edge_list *el) clear_aux_for_blocks (); } + +/* This function selects the edges in the flow graph to put on the tree + (i.e., be instrumented). The algorithm used in find_spanning_tree is not + usable for binary counters. Therefore this function uses another method. */ +static void +select_arcs_binary_coverage (struct edge_list *el) +{ + int i; + int num_edges = NUM_EDGES (el); + edge e; + + /* First add all abnormal edges to the tree. Also add all edges + to the exit block to avoid inserting profiling code behind + setting return value from function. */ + for (i = 0; i < num_edges; i++) + { + e = INDEX_EDGE (el, i); + if (((e->flags & (EDGE_ABNORMAL | EDGE_ABNORMAL_CALL | EDGE_FAKE)) + || e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun)) + && !EDGE_INFO (e)->ignore) + { + if (dump_file) + fprintf (dump_file, "Abnormal edge %d to %d put to tree\n", + e->src->index, e->dest->index); + EDGE_INFO (e)->on_tree = 1; + } + } +} + + /* Perform file-level initialization for branch-prob processing. */ void diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc index a03f1f3704f..62953515fc0 100644 --- a/gcc/tree-profile.cc +++ b/gcc/tree-profile.cc @@ -108,6 +108,7 @@ enum counter_update_method { }; static counter_update_method counter_update = COUNTER_UPDATE_SINGLE_THREAD; +static counter_update_method bin_counter_update = COUNTER_UPDATE_SINGLE_THREAD; /* These functions support measuring modified conditition/decision coverage (MC/DC). MC/DC requires all of the below during testing: @@ -1385,23 +1386,39 @@ gen_assign_counter_update (gimple_stmt_iterator *gsi, gcall *call, tree func, gsi_insert_after (gsi, call, GSI_NEW_STMT); } -/* Output instructions as GIMPLE trees to increment the COUNTER. If RESULT is - not null, then assign the updated counter value to RESULT. Insert the +/* Output instructions as GIMPLE trees to increment the COUNTER_REF. If RESULT + is not null, then assign the updated counter_ref value to RESULT. Insert the instructions to GSI. Use NAME for temporary values. */ static inline void -gen_counter_update (gimple_stmt_iterator *gsi, tree counter, tree result, - const char *name) +gen_counter_update (gimple_stmt_iterator *gsi, int counter, tree counter_ref, + tree result, const char *name) { - tree type = gcov_type_node; - tree addr = build_fold_addr_expr (counter); + tree type = get_gcov_type (counter); + tree addr = build_fold_addr_expr (counter_ref); tree one = build_int_cst (type, 1); tree relaxed = build_int_cst (integer_type_node, MEMMODEL_RELAXED); - if (counter_update == COUNTER_UPDATE_ATOMIC_BUILTIN - || (result && counter_update == COUNTER_UPDATE_ATOMIC_SPLIT)) + if (counter == GCOV_BIN_COUNTER_ARCS) { - /* __atomic_fetch_add (&counter, 1, MEMMODEL_RELAXED); */ + if (bin_counter_update == COUNTER_UPDATE_ATOMIC_BUILTIN) + { + /* __atomic_store_1 (addr, 1, MEMMODEL_RELAXED); */ + tree f = builtin_decl_explicit (BUILT_IN_ATOMIC_STORE_1); + gcall *call = gimple_build_call (f, 3, addr, one, relaxed); + gsi_insert_after (gsi, call, GSI_NEW_STMT); + } + else + { + gassign *assign + = gimple_build_assign (unshare_expr (counter_ref), one); + gsi_insert_after (gsi, assign, GSI_NEW_STMT); + } + } + else if (counter_update == COUNTER_UPDATE_ATOMIC_BUILTIN + || (result && counter_update == COUNTER_UPDATE_ATOMIC_SPLIT)) + { + /* __atomic_fetch_add (&counter_ref, 1, MEMMODEL_RELAXED); */ tree f = builtin_decl_explicit (TYPE_PRECISION (type) > 32 ? BUILT_IN_ATOMIC_ADD_FETCH_8 : BUILT_IN_ATOMIC_ADD_FETCH_4); @@ -1443,12 +1460,12 @@ gen_counter_update (gimple_stmt_iterator *gsi, tree counter, tree result, else { tree tmp1 = make_temp_ssa_name (type, NULL, name); - gassign *assign1 = gimple_build_assign (tmp1, counter); + gassign *assign1 = gimple_build_assign (tmp1, counter_ref); gsi_insert_after (gsi, assign1, GSI_NEW_STMT); tree tmp2 = make_temp_ssa_name (type, NULL, name); gassign *assign2 = gimple_build_assign (tmp2, PLUS_EXPR, tmp1, one); gsi_insert_after (gsi, assign2, GSI_NEW_STMT); - gassign *assign3 = gimple_build_assign (unshare_expr (counter), tmp2); + gassign *assign3 = gimple_build_assign (unshare_expr (counter_ref), tmp2); gsi_insert_after (gsi, assign3, GSI_NEW_STMT); if (result) { @@ -1465,8 +1482,11 @@ void gimple_gen_edge_profiler (int edgeno, edge e) { gimple_stmt_iterator gsi = gsi_last (PENDING_STMT (e)); - tree counter = tree_coverage_counter_ref (GCOV_COUNTER_ARCS, edgeno); - gen_counter_update (&gsi, counter, NULL_TREE, "PROF_edge_counter"); + int counter + = binary_coverage_flag ? GCOV_BIN_COUNTER_ARCS : GCOV_COUNTER_ARCS; + tree counter_ref = tree_coverage_counter_ref (counter, edgeno); + gen_counter_update (&gsi, counter, counter_ref, NULL_TREE, + "PROF_edge_counter"); } /* Emits code to get VALUE to instrument at GSI, and returns the @@ -1714,8 +1734,8 @@ gimple_gen_time_profiler (unsigned tag) /* Emit: counters[0] = ++__gcov_time_profiler_counter. */ gsi = gsi_start_bb (update_bb); - gen_counter_update (&gsi, tree_time_profiler_counter, original_ref, - "PROF_time_profile"); + gen_counter_update (&gsi, GCOV_TIME_PROFILER, tree_time_profiler_counter, + original_ref, "PROF_time_profile"); } /* Output instructions as GIMPLE trees to increment the average histogram @@ -1835,6 +1855,13 @@ include_source_file_for_profile (const char *filename) return false; } +#ifndef HAVE_sync_compare_and_swapqi +#define HAVE_sync_compare_and_swapqi 0 +#endif +#ifndef HAVE_atomic_compare_and_swapqi +#define HAVE_atomic_compare_and_swapqi 0 +#endif + #ifndef HAVE_sync_compare_and_swapsi #define HAVE_sync_compare_and_swapsi 0 #endif @@ -1862,6 +1889,8 @@ tree_profiling (void) bool can_support_atomic = targetm.have_libatomic; unsigned HOST_WIDE_INT gcov_type_size = tree_to_uhwi (TYPE_SIZE_UNIT (get_gcov_type ())); + bool have_atomic_1 + = HAVE_sync_compare_and_swapqi || HAVE_atomic_compare_and_swapqi; bool have_atomic_4 = HAVE_sync_compare_and_swapsi || HAVE_atomic_compare_and_swapsi; bool have_atomic_8 @@ -1875,6 +1904,10 @@ tree_profiling (void) can_support_atomic = have_atomic_8; } + if (binary_coverage_flag && flag_profile_update != PROFILE_UPDATE_SINGLE + && have_atomic_1) + bin_counter_update = COUNTER_UPDATE_ATOMIC_BUILTIN; + if (flag_profile_update == PROFILE_UPDATE_ATOMIC && !can_support_atomic) { diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in index 8b8fe6e9a28..9ae95fae063 100644 --- a/libgcc/Makefile.in +++ b/libgcc/Makefile.in @@ -907,7 +907,8 @@ iter-items := $(LIBUNWIND) # Build libgcov components. LIBGCOV_MERGE = _gcov_merge_add _gcov_merge_topn \ - _gcov_merge_ior _gcov_merge_time_profile + _gcov_merge_ior _gcov_merge_time_profile \ + _gcov_merge_bin LIBGCOV_PROFILER = _gcov_interval_profiler \ _gcov_interval_profiler_atomic \ _gcov_pow2_profiler \ diff --git a/libgcc/libgcov-driver.c b/libgcc/libgcov-driver.c index 9b305de2e7a..35a1311ee99 100644 --- a/libgcc/libgcov-driver.c +++ b/libgcc/libgcov-driver.c @@ -29,10 +29,11 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see /* Return 1, if all counter values are zero, otherwise 0. */ static inline int -are_all_counters_zero (const struct gcov_ctr_info *ci_ptr) +are_all_counters_zero (unsigned counter_tag, const struct gcov_ctr_info *ci_ptr) { for (unsigned i = 0; i < ci_ptr->num; i++) - if (ci_ptr->values[i] != 0) + if ((counter_tag == GCOV_BIN_COUNTER_ARCS && ci_ptr->bin_values[i] != 0) + || (counter_tag != GCOV_BIN_COUNTER_ARCS && ci_ptr->values[i] != 0)) return 0; return 1; @@ -152,7 +153,6 @@ buffer_fn_data (const char *filename, const struct gcov_info *gi_ptr, for (n_ctrs = ix = 0; ix != GCOV_COUNTERS; ix++) { gcov_unsigned_t length; - gcov_type *values; if (!gi_ptr->merge[ix]) continue; @@ -162,18 +162,37 @@ buffer_fn_data (const char *filename, const struct gcov_info *gi_ptr, len = 0; goto fail; } + if (ix == GCOV_BIN_COUNTER_ARCS) + { + char *values; + + len = length = GCOV_TAG_BIN_COUNTER_NUM (gcov_read_unsigned ()); + values = (char *) xmalloc (len); + if (!values) + goto fail; + + fn_buffer->info.ctrs[n_ctrs].num = length; + fn_buffer->info.ctrs[n_ctrs].bin_values = values; + + while (length--) + *values++ = gcov_read_bin_counter (); + } + else + { + gcov_type *values; - length = GCOV_TAG_COUNTER_NUM (gcov_read_unsigned ()); - len = length * sizeof (gcov_type); - values = (gcov_type *) xmalloc (len); - if (!values) - goto fail; + length = GCOV_TAG_COUNTER_NUM (gcov_read_unsigned ()); + len = length * sizeof (gcov_type); + values = (gcov_type *) xmalloc (len); + if (!values) + goto fail; - fn_buffer->info.ctrs[n_ctrs].num = length; - fn_buffer->info.ctrs[n_ctrs].values = values; + fn_buffer->info.ctrs[n_ctrs].num = length; + fn_buffer->info.ctrs[n_ctrs].values = values; - while (length--) - *values++ = gcov_read_counter (); + while (length--) + *values++ = gcov_read_counter (); + } n_ctrs++; } @@ -336,7 +355,10 @@ merge_one_data (const char *filename, int read_length = (int)gcov_read_unsigned (); length = abs (read_length); if (tag != GCOV_TAG_FOR_COUNTER (t_ix) + || (length != GCOV_TAG_BIN_COUNTER_LENGTH (ci_ptr->num) + && t_ix == GCOV_BIN_COUNTER_ARCS) || (length != GCOV_TAG_COUNTER_LENGTH (ci_ptr->num) + && t_ix != GCOV_BIN_COUNTER_ARCS && t_ix != GCOV_COUNTER_V_TOPN && t_ix != GCOV_COUNTER_V_INDIR)) goto read_mismatch; @@ -410,6 +432,15 @@ dump_counter (gcov_type counter, dump_unsigned (0, dump_fn, arg); } +/* Dump the BIN_COUNTER using the DUMP handler called with ARG. */ + +static inline void +dump_bin_counter (char bin_counter, + void (*dump_fn) (const void *, unsigned, void *), void *arg) +{ + (*dump_fn) (&bin_counter, 1, arg); +} + /* Dump the STRING using the DUMP handler called with ARG. */ static inline void @@ -577,16 +608,31 @@ write_one_data (const struct gcov_info *gi_ptr, else { dump_unsigned (GCOV_TAG_FOR_COUNTER (t_ix), dump_fn, arg); - if (are_all_counters_zero (ci_ptr)) - /* Do not stream when all counters are zero. */ - dump_unsigned (GCOV_TAG_COUNTER_LENGTH (-n_counts), - dump_fn, arg); + if (are_all_counters_zero (t_ix, ci_ptr)) + { + /* Do not stream when all counters are zero. */ + if (t_ix == GCOV_BIN_COUNTER_ARCS) + dump_unsigned (GCOV_TAG_BIN_COUNTER_LENGTH (-n_counts), + dump_fn, arg); + else + dump_unsigned (GCOV_TAG_COUNTER_LENGTH (-n_counts), dump_fn, + arg); + } else { - dump_unsigned (GCOV_TAG_COUNTER_LENGTH (n_counts), - dump_fn, arg); + if (t_ix == GCOV_BIN_COUNTER_ARCS) + dump_unsigned (GCOV_TAG_BIN_COUNTER_LENGTH (n_counts), + dump_fn, arg); + else + dump_unsigned (GCOV_TAG_COUNTER_LENGTH (n_counts), dump_fn, + arg); for (unsigned i = 0; i < n_counts; i++) - dump_counter (ci_ptr->values[i], dump_fn, arg); + { + if (t_ix == GCOV_BIN_COUNTER_ARCS) + dump_bin_counter (ci_ptr->bin_values[i], dump_fn, arg); + else + dump_counter (ci_ptr->values[i], dump_fn, arg); + } } } @@ -681,15 +727,19 @@ gcov_do_dump (struct gcov_info *list, int run_counted, int mode) /* Compute run_max of this program run. */ gcov_type run_max = 0; for (gi_ptr = list; gi_ptr; gi_ptr = gi_ptr->next) - for (unsigned f_ix = 0; (unsigned)f_ix != gi_ptr->n_functions; f_ix++) - { - const struct gcov_ctr_info *cinfo - = &gi_ptr->functions[f_ix]->ctrs[GCOV_COUNTER_ARCS]; - - for (unsigned i = 0; i < cinfo->num; i++) - if (run_max < cinfo->values[i]) - run_max = cinfo->values[i]; - } + { + if (!gi_ptr->merge[GCOV_COUNTER_ARCS]) + continue; + for (unsigned f_ix = 0; (unsigned) f_ix != gi_ptr->n_functions; f_ix++) + { + const struct gcov_ctr_info *cinfo + = &gi_ptr->functions[f_ix]->ctrs[GCOV_COUNTER_ARCS]; + + for (unsigned i = 0; i < cinfo->num; i++) + if (run_max < cinfo->values[i]) + run_max = cinfo->values[i]; + } + } allocate_filename_struct (&gf); diff --git a/libgcc/libgcov-interface.c b/libgcc/libgcov-interface.c index 2a06851e282..99955681b52 100644 --- a/libgcc/libgcov-interface.c +++ b/libgcc/libgcov-interface.c @@ -105,9 +105,12 @@ gcov_clear (const struct gcov_info *list) if (!gi_ptr->merge[t_ix]) continue; - memset (ci_ptr->values, 0, sizeof (gcov_type) * ci_ptr->num); - ci_ptr++; - } + if (t_ix == GCOV_BIN_COUNTER_ARCS) + memset (ci_ptr->bin_values, 0, ci_ptr->num); + else + memset (ci_ptr->values, 0, sizeof (gcov_type) * ci_ptr->num); + ci_ptr++; + } } } } diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c index b06ad5032d2..e18a9ebff7f 100644 --- a/libgcc/libgcov-merge.c +++ b/libgcc/libgcov-merge.c @@ -43,6 +43,13 @@ void __gcov_merge_topn (gcov_type *counters __attribute__ ((unused)), unsigned n_counters __attribute__ ((unused))) {} #endif +#ifdef L_gcov_merge_bin +void +__gcov_merge_bin (gcov_type *counters __attribute__ ((unused)), + unsigned n_counters __attribute__ ((unused))) +{} +#endif + #else #ifdef L_gcov_merge_add @@ -135,4 +142,17 @@ __gcov_merge_topn (gcov_type *counters, unsigned n_counters) } #endif /* L_gcov_merge_topn */ +#ifdef L_gcov_merge_bin +/* The profile merging function that perfoms a bitwise OR on the counters. + It is given an array COUNTERS of N_COUNTERS old counters and it reads the + same number of counters from the gcov file. */ +void +__gcov_merge_bin (gcov_type *counters, unsigned n_counters) +{ + char *bin_counters = (char *) counters; + for (; n_counters; bin_counters++, n_counters--) + *bin_counters |= gcov_get_counter_binary (); +} +#endif /* L_gcov_merge_bin */ + #endif /* inhibit_libc */ diff --git a/libgcc/libgcov-util.c b/libgcc/libgcov-util.c index c3e3b1cf6c8..fb792496cf9 100644 --- a/libgcc/libgcov-util.c +++ b/libgcc/libgcov-util.c @@ -101,7 +101,10 @@ set_fn_ctrs (struct gcov_fn_info *fn_info) if (k_ctrs_mask[i] == 0) continue; fn_info->ctrs[j].num = k_ctrs[i].num; - fn_info->ctrs[j].values = k_ctrs[i].values; + if (i == GCOV_BIN_COUNTER_ARCS) + fn_info->ctrs[j].bin_values = k_ctrs[i].bin_values; + else + fn_info->ctrs[j].values = k_ctrs[i].values; j++; } if (k_ctrs_types == 0) @@ -201,24 +204,40 @@ tag_lines (unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED) static void tag_counters (unsigned tag, int length) { - unsigned n_counts = GCOV_TAG_COUNTER_NUM (abs (length)); - gcov_type *values; + unsigned n_counts; unsigned ix; unsigned tag_ix; tag_ix = GCOV_COUNTER_FOR_TAG (tag); gcc_assert (tag_ix < GCOV_COUNTERS); + n_counts = (tag_ix == GCOV_BIN_COUNTER_ARCS + ? GCOV_TAG_BIN_COUNTER_NUM (abs (length)) + : GCOV_TAG_COUNTER_NUM (abs (length))); k_ctrs_mask [tag_ix] = 1; gcc_assert (k_ctrs[tag_ix].num == 0); k_ctrs[tag_ix].num = n_counts; - k_ctrs[tag_ix].values = values = (gcov_type *) xcalloc (n_counts, - sizeof (gcov_type)); - gcc_assert (values); + if (tag_ix == GCOV_BIN_COUNTER_ARCS) + { + char *bin_values; + k_ctrs[tag_ix].bin_values = bin_values = (char *) xcalloc (n_counts, 1); + gcc_assert (bin_values); - if (length > 0) - for (ix = 0; ix != n_counts; ix++) - values[ix] = gcov_read_counter (); + if (length > 0) + for (ix = 0; ix != n_counts; ix++) + bin_values[ix] = gcov_read_bin_counter (); + } + else + { + gcov_type *values; + k_ctrs[tag_ix].values = values + = (gcov_type *) xcalloc (n_counts, sizeof (gcov_type)); + gcc_assert (values); + + if (length > 0) + for (ix = 0; ix != n_counts; ix++) + values[ix] = gcov_read_counter (); + } } /* Handler for reading summary tag. */ @@ -464,7 +483,7 @@ gcov_read_profile_dir (const char* dir_name, int recompute_summary ATTRIBUTE_UNU global function gcov_read_counter_mem() and gcov_get_merge_weight. */ /* We save the counter value address to this variable. */ -static gcov_type *gcov_value_buf; +static void *gcov_value_buf; /* The number of counter values to be read by current merging. */ static gcov_unsigned_t gcov_value_buf_size; @@ -482,7 +501,19 @@ gcov_read_counter_mem (void) { gcov_type ret; gcc_assert (gcov_value_buf_pos < gcov_value_buf_size); - ret = *(gcov_value_buf + gcov_value_buf_pos); + ret = *((gcov_type *) gcov_value_buf + gcov_value_buf_pos); + ++gcov_value_buf_pos; + return ret; +} + +/* Similar to gcov_read_counter_mem, but handles binary counters. */ + +char +gcov_read_bin_counter_mem (void) +{ + char ret; + gcc_assert (gcov_value_buf_pos < gcov_value_buf_size); + ret = *((char *) gcov_value_buf + gcov_value_buf_pos); ++gcov_value_buf_pos; return ret; } @@ -502,7 +533,7 @@ static void merge_wrapper (gcov_merge_fn f, gcov_type *v1, gcov_unsigned_t n1, gcov_type *v2, gcov_unsigned_t n2, unsigned w) { - gcov_value_buf = v2; + gcov_value_buf = (void *) v2; gcov_value_buf_pos = 0; gcov_value_buf_size = n2; gcov_merge_weight = w; @@ -887,6 +918,18 @@ __gcov_topn_counter_op (gcov_type *counters, unsigned n_counters, } } +/* Performing FN upon bin counters. */ + +static void +__gcov_bin_counter_op (gcov_type *counters ATTRIBUTE_UNUSED, + unsigned n_counters ATTRIBUTE_UNUSED, + counter_op_fn fn ATTRIBUTE_UNUSED, + void *data1 ATTRIBUTE_UNUSED, + void *data2 ATTRIBUTE_UNUSED) +{ + /* Do nothing. */ +} + /* Scaling the counter value V by multiplying *(float*) DATA1. */ static gcov_type @@ -969,7 +1012,7 @@ gcov_profile_normalize (struct gcov_info *profile, gcov_type max_val) gcov_type curr_max_val = 0; unsigned f_ix; unsigned int i; - float scale_factor; + float scale_factor = (float) max_val; /* Find the largest count value. */ for (gi_ptr = profile; gi_ptr; gi_ptr = gi_ptr->next) @@ -983,8 +1026,10 @@ gcov_profile_normalize (struct gcov_info *profile, gcov_type max_val) continue; ci_ptr = gfi_ptr->ctrs; - for (t_ix = 0; t_ix < 1; t_ix++) - { + if (!gi_ptr->merge[GCOV_COUNTER_ARCS]) + continue; + for (t_ix = 0; t_ix < 1; t_ix++) + { for (i = 0; i < ci_ptr->num; i++) if (ci_ptr->values[i] > curr_max_val) curr_max_val = ci_ptr->values[i]; @@ -992,7 +1037,9 @@ gcov_profile_normalize (struct gcov_info *profile, gcov_type max_val) } } - scale_factor = (float)max_val / curr_max_val; + if (curr_max_val) + scale_factor /= curr_max_val; + if (verbose) fnotice (stdout, "max_val is %" PRId64 "\n", curr_max_val); @@ -1057,6 +1104,8 @@ compute_one_gcov (const struct gcov_info *gcov_info1, if (gcov_info) { + if (!gcov_info->merge[GCOV_COUNTER_ARCS]) + return 0.0; for (f_ix = 0; f_ix < gcov_info->n_functions; f_ix++) { const struct gcov_fn_info *gfi_ptr = gcov_info->functions[f_ix]; @@ -1071,6 +1120,10 @@ compute_one_gcov (const struct gcov_info *gcov_info1, return 0.0; } + if (!gcov_info1->merge[GCOV_COUNTER_ARCS] + || !gcov_info2->merge[GCOV_COUNTER_ARCS]) + return 0.0; + for (f_ix = 0; f_ix < gcov_info1->n_functions; f_ix++) { double func_cum_1 = 0.0; @@ -1129,6 +1182,8 @@ gcov_info_count_all_cold (const struct gcov_info *gcov_info, gcov_type threshold) { unsigned f_ix; + if (!gcov_info->merge[GCOV_COUNTER_ARCS]) + return false; for (f_ix = 0; f_ix < gcov_info->n_functions; f_ix++) { diff --git a/libgcc/libgcov.h b/libgcc/libgcov.h index 1643b4440cd..46aa19a49f9 100644 --- a/libgcc/libgcov.h +++ b/libgcc/libgcov.h @@ -117,6 +117,7 @@ typedef unsigned gcov_type_unsigned __attribute__ ((mode (QI))); #define gcov_write_object_summary __gcov_write_object_summary #define gcov_read_unsigned __gcov_read_unsigned #define gcov_read_counter __gcov_read_counter +#define gcov_read_bin_counter __gcov_read_bin_counter #define gcov_read_summary __gcov_read_summary #else /* IN_GCOV_TOOL */ @@ -152,8 +153,10 @@ typedef unsigned gcov_position_t; #define L_gcov_merge_topn 1 #define L_gcov_merge_ior 1 #define L_gcov_merge_time_profile 1 +#define L_gcov_merge_bin 1 extern gcov_type gcov_read_counter_mem (); +extern char gcov_read_bin_counter_mem (); extern unsigned gcov_get_merge_weight (); extern struct gcov_info *gcov_list; @@ -197,7 +200,11 @@ extern struct gcov_info *gcov_list; struct gcov_ctr_info { gcov_unsigned_t num; /* number of counters. */ - gcov_type *values; /* their values. */ + union + { + gcov_type *values; /* values for cumulative counters. */ + char *bin_values; /* values for binary counters. */ + }; }; /* Information about a single function. This uses the trailing array @@ -309,6 +316,9 @@ extern void __gcov_merge_topn (gcov_type *, unsigned) ATTRIBUTE_HIDDEN; /* The merge function that just ors the counters together. */ extern void __gcov_merge_ior (gcov_type *, unsigned) ATTRIBUTE_HIDDEN; +/* The merge function that just ors the binary counters together. */ +extern void __gcov_merge_bin (gcov_type *, unsigned) ATTRIBUTE_HIDDEN; + /* The profiler functions. */ extern void __gcov_interval_profiler (gcov_type *, gcov_type, int, unsigned); extern void __gcov_interval_profiler_atomic (gcov_type *, gcov_type, int, @@ -405,6 +415,24 @@ gcov_get_counter_target (void) #endif } +/* Similar function as gcov_get_counter(), but handles binary counters. */ + +static inline char +gcov_get_counter_binary (void) +{ +#ifndef IN_GCOV_TOOL + /* This version is for reading binary values in libgcov runtime: + we read from gcda files. */ + + return gcov_read_bin_counter (); +#else + /* This version is for gcov-tool. We read the value from memory and we do NOT + multiply it by the merge weight. */ + + return gcov_read_bin_counter_mem (); +#endif +} + /* Add VALUE to *COUNTER and make it with atomic operation if USE_ATOMIC is true. */ diff --git a/gcc/testsuite/g++.dg/gcov/gcov-11b.C b/gcc/testsuite/g++.dg/gcov/gcov-11b.C new file mode 100644 index 00000000000..a0479d916d8 --- /dev/null +++ b/gcc/testsuite/g++.dg/gcov/gcov-11b.C @@ -0,0 +1,43 @@ +/* Check that unexecuted exception processing regions are shown + distinct from unexecuted normal regions. */ + +/* { dg-options "-fbinary-coverage" } */ +/* { dg-do run } */ + +void Baz (int i) +{ + if (i) + throw 1; +} + +void Boz () throw () +{ +} + +int main () +{ + try + { + Baz (0); /* count (1) */ + Baz (0); /* count (1) */ + } + catch (...) + { + Boz (); /* count (=====) */ + } + + try + { + Baz (1); /* count (1) */ + Baz (0); /* count (#####) */ + } + catch (...) + { + Boz (); /* count (1) */ + } + + return 0; /* count (1) */ +} + +/* { dg-final { run-gcov gcov-11b.C } } */ + diff --git a/gcc/testsuite/g++.dg/gcov/gcov-15b.C b/gcc/testsuite/g++.dg/gcov/gcov-15b.C new file mode 100644 index 00000000000..d7034b2c1ce --- /dev/null +++ b/gcc/testsuite/g++.dg/gcov/gcov-15b.C @@ -0,0 +1,26 @@ +/* { dg-options "-fbinary-coverage" } */ +// { dg-do run } + +void catchEx () // count(1) +{ + __builtin_exit (0); // count(1) + try + {} + catch (int) + {} +} + +int main () // count(1) +{ + try + { + throw 5; // count(1) + } + catch (...) // count(1) + { + catchEx (); // count(1) + } +} + +// { dg-final { run-gcov gcov-15b.C } } + diff --git a/gcc/testsuite/g++.dg/gcov/gcov-1b.C b/gcc/testsuite/g++.dg/gcov/gcov-1b.C new file mode 100644 index 00000000000..f03ce3ce932 --- /dev/null +++ b/gcc/testsuite/g++.dg/gcov/gcov-1b.C @@ -0,0 +1,314 @@ +/* Check that execution status for various C constructs are reported correctly + by gcov. */ + +/* { dg-options "-fbinary-coverage" } */ +/* { dg-do run } */ + +extern "C" void abort (void); + +int do_something (int i) +{ + return i; +} + +/* Check static inline functions. */ + +int unref_val; + +static inline int +unreferenced (int i, int j) +{ + return i - j; +} + +static inline int +uncalled (int i, int j) +{ + return i * j; +} + +static inline int +called (int i, int j) +{ + return i + j; /* count(1) */ +} + +void +call_unref () +{ + if (unref_val) /* count(1) */ + unref_val = uncalled (1, 2); + unref_val = called (unref_val, 4); /* count(1) */ +} + + +/* Check for loops. */ + +int for_val1; +int for_val2; +int for_temp; + +int +test_for1 (int n) +{ + int i; + for_temp = 1; /* count(1) */ + for (i = 0; i < n; i++) /* branch(true) */ + /* branch(end) */ + for_temp++; /* count(1) */ + return for_temp; /* count(1) */ +} + +int +test_for2 (int m, int n, int o) +{ + int i, j, k; + for_temp = 1; /* count(1) */ + for (i = 0; i < n; i++) /* branch(true) */ + /* branch(end) */ + for (j = 0; j < m; j++) /* branch(true) */ + /* branch(end) */ + for (k = 0; k < o; k++) /* branch(true) */ + /* branch(end) */ + for_temp++; /* count(1) */ + return for_temp; /* count(1) */ +} + +void +call_for () +{ + for_val1 += test_for1 (0); + for_val1 += test_for1 (2); + for_val1 += test_for1 (7); + + for_val2 += test_for2 (0, 0, 0); + for_val2 += test_for2 (1, 0, 0); + for_val2 += test_for2 (1, 3, 0); + for_val2 += test_for2 (1, 3, 1); + for_val2 += test_for2 (3, 1, 5); + for_val2 += test_for2 (3, 7, 3); +} + +/* Check the use of goto. */ + +int goto_val; + +int +test_goto1 (int f) +{ + /* branch(true) */ + if (f) /* count(1) */ + /* branch(end) */ + goto lab1; /* count(1) */ + return 1; /* count(1) */ +lab1: + return 2; /* count(1) */ +} + +int +test_goto2 (int f) +{ + int i; + /* branch(true) */ + for (i = 0; i < 10; i++) /* count(1) */ + /* branch(end) */ + if (i == f) goto lab2; /* count(1) */ + return 4; /* count(1) */ +lab2: + return 8; /* count(1) */ +} + +void +call_goto () +{ + goto_val += test_goto1 (0); + goto_val += test_goto1 (1); + goto_val += test_goto2 (3); + goto_val += test_goto2 (30); +} + +/* Check nested if-then-else statements. */ + +int ifelse_val1; +int ifelse_val2; +int ifelse_val3; + +int +test_ifelse1 (int i, int j) +{ + int result = 0; + /* branch(true) */ + if (i) /* count(1) */ + /* branch(false) */ + if (j) /* count(1) */ + /* branch(end) */ + result = do_something (4); /* count(1) */ + else + result = do_something (1024); + else + /* branch(true) */ + if (j) /* count(1) */ + /* branch(end) */ + result = do_something (1); /* count(1) */ + else + result = do_something (2); /* count(1) */ + /* branch(true) */ + if (i > j) /* count(1) */ + /* branch(end) */ + result = do_something (result*2); /* count(1) */ + /* branch(true) */ + if (i > 10) /* count(1) */ + /* branch(true) */ + if (j > 10) /* count(1) */ + /* branch(end) */ + result = do_something (result*4); /* count(1) */ + return result; /* count(1) */ +} + +int +test_ifelse2 (int i) +{ + int result = 0; + /* branch(true) */ + if (!i) /* count(1) */ + /* branch(end) */ + result = do_something (1); /* count(1) */ + /* branch(true) */ + if (i == 1) /* count(1) */ + /* branch(end) */ + result = do_something (1024); + /* branch(true) */ + if (i == 2) /* count(1) */ + /* branch(end) */ + result = do_something (2); /* count(1) */ + /* branch(true) */ + if (i == 3) /* count(1) */ + /* branch(end) */ + return do_something (8); /* count(1) */ + /* branch(true) */ + if (i == 4) /* count(1) */ + /* branch(end) */ + return do_something (2048); + return result; /* count(1) */ +} + +int +test_ifelse3 (int i, int j) +{ + int result = 1; + /* branch(true) */ + if (i > 10 && j > i && j < 20) /* count(1) */ + /* branch(end) */ + result = do_something (16); /* count(1) */ + /* branch(true) */ + if (i > 20) /* count(1) */ + /* branch(true) */ + if (j > i) /* count(1) */ + /* branch(true) */ + if (j < 30) /* count(1) */ + /* branch(end) */ + result = do_something (32); /* count(1) */ + /* branch(true) */ + if (i == 3 || j == 47 || i == j) /* count(1) */ + /* branch(end) */ + result = do_something (64); /* count(1) */ + return result; /* count(1) */ +} + +void +call_ifelse () +{ + ifelse_val1 += test_ifelse1 (0, 2); + ifelse_val1 += test_ifelse1 (0, 0); + ifelse_val1 += test_ifelse1 (1, 2); + ifelse_val1 += test_ifelse1 (10, 2); + ifelse_val1 += test_ifelse1 (11, 11); + + ifelse_val2 += test_ifelse2 (0); + ifelse_val2 += test_ifelse2 (2); + ifelse_val2 += test_ifelse2 (2); + ifelse_val2 += test_ifelse2 (2); + ifelse_val2 += test_ifelse2 (3); + ifelse_val2 += test_ifelse2 (3); + + ifelse_val3 += test_ifelse3 (11, 19); + ifelse_val3 += test_ifelse3 (25, 27); + ifelse_val3 += test_ifelse3 (11, 22); + ifelse_val3 += test_ifelse3 (11, 10); + ifelse_val3 += test_ifelse3 (21, 32); + ifelse_val3 += test_ifelse3 (21, 20); + ifelse_val3 += test_ifelse3 (1, 2); + ifelse_val3 += test_ifelse3 (32, 31); + ifelse_val3 += test_ifelse3 (3, 0); + ifelse_val3 += test_ifelse3 (0, 47); + ifelse_val3 += test_ifelse3 (65, 65); +} + +/* Check switch statements. */ + +int switch_val, switch_m; + +int +test_switch (int i, int j) +{ + int result = 0; /* count(1) */ + + /* branch(true) */ + switch (i) /* count(1) */ + /* branch(end) */ + { + case 1: /* count(1) */ + result = do_something (2); /* count(1) */ + break; /* count(1) */ + case 2: + result = do_something (1024); + break; + case 3: /* count(1) */ + case 4: + /* branch(true) */ + if (j == 2) /* count(1) */ + /* branch(end) */ + return do_something (4); /* count(1) */ + result = do_something (8); /* count(1) */ + break; /* count(1) */ + default: + result = do_something (32); /* count(1) */ + switch_m++; /* count(1) */ + break; + } + return result; /* count(1) */ +} + +void +call_switch () +{ + switch_val += test_switch (1, 0); + switch_val += test_switch (3, 0); + switch_val += test_switch (3, 2); + switch_val += test_switch (4, 0); + switch_val += test_switch (16, 0); + switch_val += switch_m; +} + +int +main() +{ + call_for (); + call_goto (); + call_ifelse (); + call_switch (); + call_unref (); + if ((for_val1 != 12) + || (for_val2 != 87) + || (goto_val != 15) + || (ifelse_val1 != 31) + || (ifelse_val2 != 23) + || (ifelse_val3 != 246) + || (switch_val != 55) + || (unref_val != 4)) + abort (); + return 0; +} + +/* { dg-final { run-gcov branches { -b gcov-1b.C } } } */ + diff --git a/gcc/testsuite/gcc.misc-tests/gcov-35.c b/gcc/testsuite/gcc.misc-tests/gcov-35.c new file mode 100644 index 00000000000..336522c2d2e --- /dev/null +++ b/gcc/testsuite/gcc.misc-tests/gcov-35.c @@ -0,0 +1,303 @@ +/* { dg-options "-fbinary-coverage" } */ +/* { dg-do run } */ + +void +noop (void) +{ +} + +int +bincov001 (void) +{ + int i; + + for (i = 0; i < 10; i++) /* count(1) */ + noop (); /* count(1) */ + + return 0; /* count(1) */ +} + +int +bincov002 (void) +{ + unsigned ix, jx = 0; + + for (ix = 10; ix--;) if (ix & 1) jx++; /* count(1) */ + + return jx != 5; +} + +int +bincov002b () +{ + unsigned ix, jx = 0; + + ix = 10; goto test; loop: ; if (ix & 1) jx++; test: ; if (ix--) goto loop; /* count(1) */ + + return jx != 5; +} + + +int bincov003_one = 1; +int bincov003_zero = 0; + +int +bincov003_foo (int ix) +{ + return ix & 1 ? bincov003_one : bincov003_zero; /* count(1) */ +} + +int +bincov003() +{ + unsigned ix, jx = 0; + + for (ix = 10; ix--;) jx += bincov003_foo (ix); /* count(1) */ + + return jx != 5; +} + +void +bincov004_bar (void) +{} + +void +bincov004_foo (int i) +{ + if (i > 1) /* count(1) */ + return; /* count(#####) */ + + bincov004_bar (); /* count(1) */ +} + +int +bincov004 (void) +{ + bincov004_foo (0); + return 0; +} + +int +bincov005 (void) +{ + int a = 1; + int b = 2; + int c = -3; + switch(a) /* count(1) */ + { + case 1: /* count(1) */ + c = 3; + switch(b) { /* count(1) */ + case 1: /* count(#####) */ + c = 4; + break; + case 2: /* count(1) */ + c = 5; + break; + } + break; + case 2: /* count(#####) */ + c = 6; + break; + default: /* count(#####) */ + break; + } + return 0; +} + +int +bincov006 (int x) +{ + if (x == 0) + { + int *ptr; +label: /* count(#####) */ + { + } + } + if (x == 1) + { + __builtin_printf("hello\n"); + } + return 0; +} + +int +bincov007 (int x) +{ + switch (x) + { + case 0: + foo: /* count(#####) */ + case 1:; + } + return 0; +} + +int bincov008_x, bincov008_y; + +static void +bincov008_foo (int a, int b) +{ + { + if (a == 1 || a == 2) /* count(1) */ + { + bincov008_x = 4; /* count(1) */ + if (b == 3) /* count(1) */ + bincov008_x = 6; /* count(1) */ + } + else + bincov008_x = 15; /* count(#####) */ + } +} + +int +bincov008 (void) +{ + bincov008_foo (2, 3); + return 0; +} + +/* Same as gcov-pr85217.c */ +int bincov009_a = 0; + +int +bincov009 (void) { + for (;; bincov009_a++) { + int c[1]; + if (bincov009_a) { + break; + bincov009_a; + continue; /* count(1) */ + } + continue; /* count(1) */ + } + + return 0; +} + +int +bincov010(void) { + int x = 0; + if (x > 0) { +if_1: + x = 1; /* count(#####) */ +if_2: + x = 2; /* count(#####) */ +if_3: + goto if_1; /* count(#####) */ + } + else if (x > 1) /* count(1) */ + { + goto if_2; /* count(#####) */ + } + return 0; /* count(1) */ +} + +/* Switch without abnormal edges */ +int +bincov011 (int x, int y) { + switch(x) { + case 0: +case_0: + x += 1; /* count(#####) */ + goto exit; + case 1: + switch(y) { /* count(1) */ + case 0: +case_00: + x += 1; /* count(#####) */ + goto exit; /* count(#####) */ + case 1: + x += 1; /* count(1) */ +case_01_1: + x += 1; /* count(1) */ +case_01_2: + break; /* count(1) */ + case 2: +case_02: + x += 1; /* count(1) */ + goto exit; + } + break; + case 2: +case_2: + x += 1; /* count(#####) */ + goto exit; + } +end_switch: + x += 1; /* count(1) */ +exit: + return 0; +} + +/* Switch with abnormal edges */ +int +bincov012 (int x, int y) { + switch(x) { + case 0: +case_0: + noop(); /* count(#####) */ + goto exit; + case 1: + switch(y) { /* count(1) */ + case 0: +case_00: + noop(); /* count(#####) */ + goto exit; /* count(#####) */ + case 1: + noop(); /* count(1) */ +case_01_1: + noop(); /* count(1) */ +case_01_2: + break; /* count(1) */ + case 2: +case_02: + noop(); /* count(1) */ + goto exit; + } + break; + case 2: +case_2: + noop(); /* count(#####) */ + goto exit; + } +end_switch: + noop(); /* count(1) */ +exit: + return 0; +} + +int +bincov013(int x) +{ + if (x) + __builtin_exit(0); /* count(1) */ + return x; /* count(1) */ +} + +int main () +{ + bincov001(); + bincov001(); + bincov002(); + bincov002(); + bincov002b(); + bincov003(); + bincov004(); + bincov005(); + bincov006(1); + bincov007(1); + bincov008(); + bincov009(); + bincov010(); + bincov011(1, 1); + bincov011(1, 2); + bincov012(1, 1); + bincov012(1, 2); + bincov013(0); + bincov013(1); + bincov013(1); /* count(#####) */ +} + +/* { dg-final { run-gcov gcov-35.c } } */ diff --git a/gcc/testsuite/gcc.misc-tests/gcov-7b.c b/gcc/testsuite/gcc.misc-tests/gcov-7b.c new file mode 100644 index 00000000000..7c4a2267390 --- /dev/null +++ b/gcc/testsuite/gcc.misc-tests/gcov-7b.c @@ -0,0 +1,87 @@ +/* Check that gcov correctly reports line counts, branch percentages, + * and call return percentages for functions that call longjmp. */ + +/* { dg-options "-fbinary-coverage" } */ +/* { dg-do run } */ + +#include <setjmp.h> + +extern void abort (void); +extern void exit (int); + +jmp_buf env; +int val; +int longjmp_taken; +int bar_enter, bar_exit; +int foo_enter, foo_exit; + +void bar (int i) +{ + bar_enter++; /* count(1) */ + /* branch(true) */ + if (i == 0) { + /* branch(end) */ + longjmp_taken++; /* count(1) */ + longjmp (env, 1); + } + val += i+1; + bar_exit++; /* count(1) */ +} + +void foo (int i) +{ + foo_enter++; /* count(1) */ + /* branch(true) */ + if (i == 1) { + /* branch(end) */ + longjmp_taken++; /* count(1) */ + longjmp (env, 2); + } + /* returns(true) */ + bar (i); /* count(1) */ + /* returns(true) */ + bar (7); /* count(1) */ + /* returns(end) */ + val += 16; + foo_exit++; /* count(1) */ +} + +int +passed () +{ + return (val == 31 && + longjmp_taken == 2 && + foo_enter == 3 && + foo_exit == 1 && + bar_enter == 3 && + bar_exit == 2); + +} + +void +leave (int i) +{ + if (i == 0) { + abort (); + } + exit (0); +} + +int +main() +{ + int retval; + + /* branch(true) */ + if ((retval = setjmp (env))) { + /* branch(end) */ + val += retval; /* count(1) */ + } + /* returns(true) */ + foo (val); /* count(1) */ + /* returns(end) */ + leave (passed()); /* count(1) */ +} + +/* { dg-final { run-gcov calls branches { -b gcov-7b.c } } } */ + diff --git a/gcc/testsuite/lib/gcov.exp b/gcc/testsuite/lib/gcov.exp index e62a39e7ed5..ff465a7fa18 100644 --- a/gcc/testsuite/lib/gcov.exp +++ b/gcc/testsuite/lib/gcov.exp @@ -150,6 +150,21 @@ proc verify-branches { testname testcase file } { set shouldbe [lreplace $shouldbe $i $i [expr 100 - $num]] } } + } elseif [regexp "branch\\((\[a-z\]+)\\)" "$line" all returns] { + # All percentages in the current list should have been seen. + if {[llength $shouldbe] != 0} { + fail "$testname line $n: expected return percentages not found: $shouldbe" + incr failed + set shouldbe "" + } + # Record the string to check for. + if {$returns == "true"} { + # set shouldbe [list "taken"] + set shouldbe "1" + } elseif {$returns == "false"} { + # set shouldbe [list "not taken"] + set shouldbe "0" + } } elseif [regexp "branch +\[0-9\]+ taken (-\[0-9\]+)%" "$line" \ all taken] { # Percentages should never be negative. @@ -172,6 +187,17 @@ proc verify-branches { testname testcase file } { if {$i != -1} { set shouldbe [lreplace $shouldbe $i $i] } + } elseif [regexp "branch +\[0-9\]+ (not taken|taken)" "$line" all returns] { + # If this string is one to check for then remove it + # from the list. It's normal to ignore some reports. + if {$returns == "not taken"} { + set i [lsearch $shouldbe "0"] + } elseif {$returns == "taken"} { + set i [lsearch $shouldbe "1"] + } + if {$i != -1} { + set shouldbe [lreplace $shouldbe $i $i] + } } elseif [regexp "branch\\(end\\)" "$line"] { # All percentages in the list should have been seen by now. if {[llength $shouldbe] != 0} { @@ -476,6 +502,19 @@ proc verify-calls { testname testcase file } { } # Record the percentages to check for. set shouldbe $new_shouldbe + } elseif [regexp "returns\\((\[a-z\]+)\\)" "$line" all returns] { + # All percentages in the current list should have been seen. + if {[llength $shouldbe] != 0} { + fail "$testname line $n: expected return strings not found: $shouldbe" + incr failed + set shouldbe "" + } + # Record the string to check for. + if {$returns == "true"} { + set shouldbe "1" + } elseif {$returns == "false"} { + set shouldbe "0" + } } elseif [regexp "call +\[0-9\]+ returned (-\[0-9\]+)%" "$line" \ all returns] { # Percentages should never be negative. @@ -493,6 +532,17 @@ proc verify-calls { testname testcase file } { if {$i != -1} { set shouldbe [lreplace $shouldbe $i $i] } + } elseif [regexp "call +\[0-9\]+ (never returned|returned)$" "$line" all returns] { + # If this string is one to check for then remove it + # from the list. It's normal to ignore some reports. + if {$returns == "never returned"} { + set i [lsearch $shouldbe "0"] + } elseif {$returns == "returned"} { + set i [lsearch $shouldbe "1"] + } + if {$i != -1} { + set shouldbe [lreplace $shouldbe $i $i] + } } elseif [regexp "returns\\(end\\)" "$line"] { # All percentages in the list should have been seen by now. if {[llength $shouldbe] != 0} { -- 2.54.0
