Hello. This patch is follow-up of the series where I introduce a set of counter update function that are thread-safe. I originally thought that majority of profile corruptions are caused by non-atomic updated of CFG (-fprofile-arc). But there are multiple counters that compare it's count to a number of execution of a basic block:
blake2s.cpp:150:40: error: corrupted value profile: value profile counter (11301120 out of 11314388) inconsistent with basic-block count (11555117) memcpy( S->buf + left, in, fill ); // Fill buffer This can be seen for unrar binary: PR58306. I'm also adding a simple test-case which reveals the inconsistency: val-profiler-threads-1.c. I've been running regression tests, ready to install after it finishes? Thanks, Martin
From a55681992f572b0591a24e82715ddc01330230e0 Mon Sep 17 00:00:00 2001 From: marxin <mli...@suse.cz> Date: Mon, 8 Aug 2016 15:44:28 +0200 Subject: [PATCH 5/5] Add new *_atomic counter update function (-fprofile-update=atomic) libgcc/ChangeLog: 2016-08-08 Martin Liska <mli...@suse.cz> PR gcov-profile/58306 * Makefile.in: New functions (modules) are added. * libgcov-profiler.c (__gcov_interval_profiler_atomic): New function. (__gcov_pow2_profiler_atomic): New function. (__gcov_one_value_profiler_body): New argument is instroduced. (__gcov_one_value_profiler): Call with the new argument. (__gcov_one_value_profiler_atomic): Likewise. (__gcov_indirect_call_profiler_v2): Likewise. (__gcov_time_profiler_atomic): New function. (__gcov_average_profiler_atomic): Likewise. (__gcov_ior_profiler_atomic): Likewise. * libgcov.h: Declare the aforementioned functions. gcc/testsuite/ChangeLog: 2016-08-08 Martin Liska <mli...@suse.cz> PR gcov-profile/58306 * gcc.dg/tree-prof/val-profiler-threads-1.c: New test. gcc/ChangeLog: 2016-08-08 Martin Liska <mli...@suse.cz> PR gcov-profile/58306 * tree-profile.c (gimple_init_edge_profiler): Create conditionally atomic variants of profile update functions. --- .../gcc.dg/tree-prof/val-profiler-threads-1.c | 41 +++++++++ gcc/tree-profile.c | 36 ++++---- libgcc/Makefile.in | 15 +++- libgcc/libgcov-profiler.c | 97 ++++++++++++++++++++-- libgcc/libgcov.h | 10 +++ 5 files changed, 173 insertions(+), 26 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/tree-prof/val-profiler-threads-1.c diff --git a/gcc/testsuite/gcc.dg/tree-prof/val-profiler-threads-1.c b/gcc/testsuite/gcc.dg/tree-prof/val-profiler-threads-1.c new file mode 100644 index 0000000..0f7477e --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-prof/val-profiler-threads-1.c @@ -0,0 +1,41 @@ +/* { dg-options "-O0 -pthread -fprofile-update=atomic" } */ +#include <pthread.h> + +#define NUM_THREADS 8 +#define SIZE 1024 +#define ITERATIONS (1000 * 1000) + +char buffer[SIZE]; +char buffer2[SIZE]; + +void *copy_memory(char *dst, char *src, unsigned size) +{ + for (unsigned i = 0; i < ITERATIONS; i++) + { + dst[size % 10] = src[size % 20]; + } +} + +void *foo(void *d) +{ + copy_memory (buffer, buffer2, SIZE); +} + +int main(int argc, char *argv[]) +{ + pthread_t threads[NUM_THREADS]; + int rc; + long t; + for(t=0;t<NUM_THREADS;t++){ + rc = pthread_create(&threads[t], NULL, foo, 0); + if (rc){ + return 1; + } + } + + int retval; + for(t=0;t<NUM_THREADS;t++) + pthread_join (threads[t], (void**)&retval); + + return buffer[10]; +} diff --git a/gcc/tree-profile.c b/gcc/tree-profile.c index 740f7ab..0b29e72 100644 --- a/gcc/tree-profile.c +++ b/gcc/tree-profile.c @@ -128,9 +128,13 @@ gimple_init_edge_profiler (void) tree average_profiler_fn_type; tree time_profiler_fn_type; const char *profiler_fn_name; + const char *fn_name; if (!gcov_type_node) { + const char *fn_suffix + = flag_profile_update == PROFILE_UPDATE_ATOMIC ? "_atomic" : ""; + gcov_type_node = get_gcov_type (); gcov_type_ptr = build_pointer_type (gcov_type_node); @@ -140,9 +144,9 @@ gimple_init_edge_profiler (void) gcov_type_ptr, gcov_type_node, integer_type_node, unsigned_type_node, NULL_TREE); - tree_interval_profiler_fn - = build_fn_decl ("__gcov_interval_profiler", - interval_profiler_fn_type); + fn_name = concat ("__gcov_interval_profiler", fn_suffix, NULL); + tree_interval_profiler_fn = build_fn_decl (fn_name, + interval_profiler_fn_type); TREE_NOTHROW (tree_interval_profiler_fn) = 1; DECL_ATTRIBUTES (tree_interval_profiler_fn) = tree_cons (get_identifier ("leaf"), NULL, @@ -153,8 +157,8 @@ gimple_init_edge_profiler (void) = build_function_type_list (void_type_node, gcov_type_ptr, gcov_type_node, NULL_TREE); - tree_pow2_profiler_fn = build_fn_decl ("__gcov_pow2_profiler", - pow2_profiler_fn_type); + fn_name = concat ("__gcov_pow2_profiler", fn_suffix, NULL); + tree_pow2_profiler_fn = build_fn_decl (fn_name, pow2_profiler_fn_type); TREE_NOTHROW (tree_pow2_profiler_fn) = 1; DECL_ATTRIBUTES (tree_pow2_profiler_fn) = tree_cons (get_identifier ("leaf"), NULL, @@ -165,9 +169,9 @@ gimple_init_edge_profiler (void) = build_function_type_list (void_type_node, gcov_type_ptr, gcov_type_node, NULL_TREE); - tree_one_value_profiler_fn - = build_fn_decl ("__gcov_one_value_profiler", - one_value_profiler_fn_type); + fn_name = concat ("__gcov_one_value_profiler", fn_suffix, NULL); + tree_one_value_profiler_fn = build_fn_decl (fn_name, + one_value_profiler_fn_type); TREE_NOTHROW (tree_one_value_profiler_fn) = 1; DECL_ATTRIBUTES (tree_one_value_profiler_fn) = tree_cons (get_identifier ("leaf"), NULL, @@ -197,9 +201,8 @@ gimple_init_edge_profiler (void) time_profiler_fn_type = build_function_type_list (void_type_node, gcov_type_ptr, NULL_TREE); - tree_time_profiler_fn - = build_fn_decl ("__gcov_time_profiler", - time_profiler_fn_type); + fn_name = concat ("__gcov_time_profiler", fn_suffix, NULL); + tree_time_profiler_fn = build_fn_decl (fn_name, time_profiler_fn_type); TREE_NOTHROW (tree_time_profiler_fn) = 1; DECL_ATTRIBUTES (tree_time_profiler_fn) = tree_cons (get_identifier ("leaf"), NULL, @@ -209,16 +212,15 @@ gimple_init_edge_profiler (void) average_profiler_fn_type = build_function_type_list (void_type_node, gcov_type_ptr, gcov_type_node, NULL_TREE); - tree_average_profiler_fn - = build_fn_decl ("__gcov_average_profiler", - average_profiler_fn_type); + fn_name = concat ("__gcov_average_profiler", fn_suffix, NULL); + tree_average_profiler_fn = build_fn_decl (fn_name, + average_profiler_fn_type); TREE_NOTHROW (tree_average_profiler_fn) = 1; DECL_ATTRIBUTES (tree_average_profiler_fn) = tree_cons (get_identifier ("leaf"), NULL, DECL_ATTRIBUTES (tree_average_profiler_fn)); - tree_ior_profiler_fn - = build_fn_decl ("__gcov_ior_profiler", - average_profiler_fn_type); + fn_name = concat ("__gcov_ior_profiler", fn_suffix, NULL); + tree_ior_profiler_fn = build_fn_decl (fn_name, average_profiler_fn_type); TREE_NOTHROW (tree_ior_profiler_fn) = 1; DECL_ATTRIBUTES (tree_ior_profiler_fn) = tree_cons (get_identifier ("leaf"), NULL, diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in index efaf7f7..f0cbc92 100644 --- a/libgcc/Makefile.in +++ b/libgcc/Makefile.in @@ -858,10 +858,19 @@ include $(iterator) LIBGCOV_MERGE = _gcov_merge_add _gcov_merge_single _gcov_merge_delta \ _gcov_merge_ior _gcov_merge_time_profile _gcov_merge_icall_topn -LIBGCOV_PROFILER = _gcov_interval_profiler _gcov_pow2_profiler \ +LIBGCOV_PROFILER = _gcov_interval_profiler \ + _gcov_interval_profiler_atomic \ + _gcov_pow2_profiler \ + _gcov_pow2_profiler_atomic \ _gcov_one_value_profiler \ - _gcov_average_profiler _gcov_ior_profiler \ - _gcov_indirect_call_profiler_v2 _gcov_time_profiler \ + _gcov_one_value_profiler_atomic \ + _gcov_average_profiler \ + _gcov_average_profiler_atomic \ + _gcov_ior_profiler \ + _gcov_ior_profiler_atomic \ + _gcov_indirect_call_profiler_v2 \ + _gcov_time_profiler \ + _gcov_time_profiler_atomic \ _gcov_indirect_call_topn_profiler LIBGCOV_INTERFACE = _gcov_dump _gcov_flush _gcov_fork \ _gcov_execl _gcov_execlp \ diff --git a/libgcc/libgcov-profiler.c b/libgcc/libgcov-profiler.c index 6b4557a..0837ea1 100644 --- a/libgcc/libgcov-profiler.c +++ b/libgcc/libgcov-profiler.c @@ -46,6 +46,26 @@ __gcov_interval_profiler (gcov_type *counters, gcov_type value, } #endif +#ifdef L_gcov_interval_profiler_atomic +/* If VALUE is in interval <START, START + STEPS - 1>, then increases the + corresponding counter in COUNTERS. If the VALUE is above or below + the interval, COUNTERS[STEPS] or COUNTERS[STEPS + 1] is increased + instead. Function is thread-safe. */ + +void +__gcov_interval_profiler_atomic (gcov_type *counters, gcov_type value, + int start, unsigned steps) +{ + gcov_type delta = value - start; + if (delta < 0) + __atomic_fetch_add (&counters[steps + 1], 1, MEMMODEL_RELAXED); + else if (delta >= steps) + __atomic_fetch_add (&counters[steps], 1, MEMMODEL_RELAXED); + else + __atomic_fetch_add (&counters[delta], 1, MEMMODEL_RELAXED); +} +#endif + #ifdef L_gcov_pow2_profiler /* If VALUE is a power of two, COUNTERS[1] is incremented. Otherwise COUNTERS[0] is incremented. */ @@ -60,6 +80,21 @@ __gcov_pow2_profiler (gcov_type *counters, gcov_type value) } #endif +#ifdef L_gcov_pow2_profiler_atomic +/* If VALUE is a power of two, COUNTERS[1] is incremented. Otherwise + COUNTERS[0] is incremented. Function is thread-safe. */ + +void +__gcov_pow2_profiler_atomic (gcov_type *counters, gcov_type value) +{ + if (value & (value - 1)) + __atomic_fetch_add (&counters[0], 1, MEMMODEL_RELAXED); + else + __atomic_fetch_add (&counters[1], 1, MEMMODEL_RELAXED); +} +#endif + + /* Tries to determine the most common value among its inputs. Checks if the value stored in COUNTERS[0] matches VALUE. If this is the case, COUNTERS[1] is incremented. If this is not the case and COUNTERS[1] is not zero, @@ -68,10 +103,12 @@ __gcov_pow2_profiler (gcov_type *counters, gcov_type value) function is called more than 50% of the time with one value, this value will be in COUNTERS[0] in the end. - In any case, COUNTERS[2] is incremented. */ + In any case, COUNTERS[2] is incremented. If USE_ATOMIC is set to 1, + COUNTERS[2] is updated with an atomic instruction. */ static inline void -__gcov_one_value_profiler_body (gcov_type *counters, gcov_type value) +__gcov_one_value_profiler_body (gcov_type *counters, gcov_type value, + int use_atomic) { if (value == counters[0]) counters[1]++; @@ -82,14 +119,26 @@ __gcov_one_value_profiler_body (gcov_type *counters, gcov_type value) } else counters[1]--; - counters[2]++; + + if (use_atomic) + __atomic_fetch_add (&counters[2], 1, MEMMODEL_RELAXED); + else + counters[2]++; } #ifdef L_gcov_one_value_profiler void __gcov_one_value_profiler (gcov_type *counters, gcov_type value) { - __gcov_one_value_profiler_body (counters, value); + __gcov_one_value_profiler_body (counters, value, 0); +} +#endif + +#ifdef L_gcov_one_value_profiler_atomic +void +__gcov_one_value_profiler_atomic (gcov_type *counters, gcov_type value) +{ + __gcov_one_value_profiler_body (counters, value, 1); } #endif @@ -265,14 +314,14 @@ __gcov_indirect_call_profiler_v2 (gcov_type value, void* cur_func) if (cur_func == __gcov_indirect_call_callee || (__LIBGCC_VTABLE_USES_DESCRIPTORS__ && __gcov_indirect_call_callee && *(void **) cur_func == *(void **) __gcov_indirect_call_callee)) - __gcov_one_value_profiler_body (__gcov_indirect_call_counters, value); + __gcov_one_value_profiler_body (__gcov_indirect_call_counters, value, 0); } #endif #ifdef L_gcov_time_profiler /* Counter for first visit of each function. */ -static gcov_type function_counter; +gcov_type function_counter; /* Sets corresponding COUNTERS if there is no value. */ @@ -284,6 +333,19 @@ __gcov_time_profiler (gcov_type* counters) } #endif +/* Sets corresponding COUNTERS if there is no value. + Function is thread-safe. */ + +#ifdef L_gcov_time_profiler_atomic +void +__gcov_time_profiler_atomic (gcov_type* counters) +{ + if (!counters[0]) + counters[0] = __atomic_add_fetch (&function_counter, 1, MEMMODEL_RELAXED); +} +#endif + + #ifdef L_gcov_average_profiler /* Increase corresponding COUNTER by VALUE. FIXME: Perhaps we want to saturate up. */ @@ -296,6 +358,18 @@ __gcov_average_profiler (gcov_type *counters, gcov_type value) } #endif +#ifdef L_gcov_average_profiler_atomic +/* Increase corresponding COUNTER by VALUE. FIXME: Perhaps we want + to saturate up. Function is thread-safe. */ + +void +__gcov_average_profiler_atomic (gcov_type *counters, gcov_type value) +{ + __atomic_fetch_add (&counters[0], value, MEMMODEL_RELAXED); + __atomic_fetch_add (&counters[1], 1, MEMMODEL_RELAXED); +} +#endif + #ifdef L_gcov_ior_profiler /* Bitwise-OR VALUE into COUNTER. */ @@ -306,4 +380,15 @@ __gcov_ior_profiler (gcov_type *counters, gcov_type value) } #endif +#ifdef L_gcov_ior_profiler_atomic +/* Bitwise-OR VALUE into COUNTER. Function is thread-safe. */ + +void +__gcov_ior_profiler_atomic (gcov_type *counters, gcov_type value) +{ + __atomic_fetch_or (&counters[0], value, MEMMODEL_RELAXED); +} +#endif + + #endif /* inhibit_libc */ diff --git a/libgcc/libgcov.h b/libgcc/libgcov.h index 80f13e2..82f620a 100644 --- a/libgcc/libgcov.h +++ b/libgcc/libgcov.h @@ -268,15 +268,25 @@ extern void __gcov_merge_icall_topn (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, + unsigned); extern void __gcov_pow2_profiler (gcov_type *, gcov_type); +extern void __gcov_pow2_profiler_atomic (gcov_type *, gcov_type); extern void __gcov_one_value_profiler (gcov_type *, gcov_type); +extern void __gcov_one_value_profiler_atomic (gcov_type *, gcov_type); extern void __gcov_indirect_call_profiler_v2 (gcov_type, void *); extern void __gcov_time_profiler (gcov_type *); +extern void __gcov_time_profiler_atomic (gcov_type *); extern void __gcov_average_profiler (gcov_type *, gcov_type); +extern void __gcov_average_profiler_atomic (gcov_type *, gcov_type); extern void __gcov_ior_profiler (gcov_type *, gcov_type); +extern void __gcov_ior_profiler_atomic (gcov_type *, gcov_type); extern void __gcov_indirect_call_topn_profiler (gcov_type, void *); extern void gcov_sort_n_vals (gcov_type *, int); +/* Counter for first visit of each function. */ +extern gcov_type function_counter; + #ifndef inhibit_libc /* The wrappers around some library functions.. */ extern pid_t __gcov_fork (void) ATTRIBUTE_HIDDEN; -- 2.9.2