Message ID | 20150318223609.GQ1746@tucnak.redhat.com |
---|---|
State | New |
Headers | show |
On March 18, 2015 11:36:09 PM GMT+01:00, Jakub Jelinek <jakub@redhat.com> wrote: >Hi! > >TSAN_FUNC_EXIT internal function is special, because we drop it during >inlining, so for fnsplit we need to be careful about it, otherwise we >can >end up with unbalanced pairs of tsan entry/exit marker functions. > >This patch gives up unless it is one of the two most common cases with >-fsanitize=thread - either TSAN_FUNC_EXIT in the return bb, which is >handled >as accepting it as return bb despite the TSAN_FUNC_EXIT call - >duplicating >the TSAN_FUNC_EXIT is exactly the right thing to do in that case, we >want it >in the *.part.* function and also in the caller, if the former is later >inlined into the latter again, the TSAN_FUNC_EXIT of the former is >dropped. >And another case that we handle is if there is TSAN_FUNC_EXIT on the >predecessors of return_bb - we can handle that case easily too by >duplicating TSAN_FUNC_EXIT after the *.part.* call. > >Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk? OK. Thanks, Richard. >2015-03-18 Jakub Jelinek <jakub@redhat.com> > > PR sanitizer/65400 > * ipa-split.c (find_return_bb): Allow TSAN_FUNC_EXIT internal > call in the return bb. > (find_split_points): Add RETURN_BB argument, don't call > find_return_bb. > (split_function): Likewise. Add ADD_TSAN_FUNC_EXIT argument, > if true append TSAN_FUNC_EXIT internal call after the call to > the split off function. > (execute_split_functions): Call find_return_bb here. > Don't optimize if TSAN_FUNC_EXIT is found in unexpected places. > Adjust find_split_points and split_function calls. > > * c-c++-common/tsan/pr65400-1.c: New test. > * c-c++-common/tsan/pr65400-2.c: New test. > >--- gcc/ipa-split.c.jj 2015-03-17 18:10:11.000000000 +0100 >+++ gcc/ipa-split.c 2015-03-18 17:12:45.017773858 +0100 >@@ -769,7 +769,8 @@ consider_split (struct split_point *curr > of the form: > <retval> = tmp_var; > return <retval> >- but return_bb can not be more complex than this. >+ but return_bb can not be more complex than this (except for >+ -fsanitize=thread we allow TSAN_FUNC_EXIT () internal call in >there). > If nothing is found, return the exit block. > > When there are multiple RETURN statement, chose one with return value, >@@ -814,6 +815,13 @@ find_return_bb (void) > found_return = true; > retval = gimple_return_retval (return_stmt); > } >+ /* For -fsanitize=thread, allow also TSAN_FUNC_EXIT () in the >return >+ bb. */ >+ else if ((flag_sanitize & SANITIZE_THREAD) >+ && is_gimple_call (stmt) >+ && gimple_call_internal_p (stmt) >+ && gimple_call_internal_fn (stmt) == IFN_TSAN_FUNC_EXIT) >+ ; > else > break; > } >@@ -1074,12 +1082,11 @@ typedef struct > the component used by consider_split. */ > > static void >-find_split_points (int overall_time, int overall_size) >+find_split_points (basic_block return_bb, int overall_time, int >overall_size) > { > stack_entry first; > vec<stack_entry> stack = vNULL; > basic_block bb; >- basic_block return_bb = find_return_bb (); > struct split_point current; > > current.header_time = overall_time; >@@ -1236,19 +1243,20 @@ insert_bndret_call_after (tree retbnd, t > gimple_call_set_lhs (bndret, retbnd); > gsi_insert_after (gsi, bndret, GSI_CONTINUE_LINKING); > } >+ > /* Split function at SPLIT_POINT. */ > > static void >-split_function (struct split_point *split_point) >+split_function (basic_block return_bb, struct split_point >*split_point, >+ bool add_tsan_func_exit) > { > vec<tree> args_to_pass = vNULL; > bitmap args_to_skip; > tree parm; > int num = 0; >cgraph_node *node, *cur_node = cgraph_node::get >(current_function_decl); >- basic_block return_bb = find_return_bb (); > basic_block call_bb; >- gcall *call; >+ gcall *call, *tsan_func_exit_call = NULL; > edge e; > edge_iterator ei; > tree retval = NULL, real_retval = NULL, retbnd = NULL; >@@ -1534,11 +1542,18 @@ split_function (struct split_point *spli > || DECL_BY_REFERENCE (DECL_RESULT (current_function_decl)))) > gimple_call_set_return_slot_opt (call, true); > >+ if (add_tsan_func_exit) >+ tsan_func_exit_call = gimple_build_call_internal >(IFN_TSAN_FUNC_EXIT, 0); >+ > /* Update return value. This is bit tricky. When we do not return, > do nothing. When we return we might need to update return_bb > or produce a new return statement. */ > if (!split_part_return_p) >- gsi_insert_after (&gsi, call, GSI_NEW_STMT); >+ { >+ gsi_insert_after (&gsi, call, GSI_NEW_STMT); >+ if (tsan_func_exit_call) >+ gsi_insert_after (&gsi, tsan_func_exit_call, GSI_NEW_STMT); >+ } > else > { > e = make_edge (call_bb, return_bb, >@@ -1642,6 +1657,8 @@ split_function (struct split_point *spli > } > else > gsi_insert_after (&gsi, call, GSI_NEW_STMT); >+ if (tsan_func_exit_call) >+ gsi_insert_after (&gsi, tsan_func_exit_call, GSI_NEW_STMT); > } > /* We don't use return block (there is either no return in function or > multiple of them). So create new basic block with return statement. >@@ -1684,6 +1701,8 @@ split_function (struct split_point *spli > /* Build bndret call to obtain returned bounds. */ > if (retbnd) > insert_bndret_call_after (retbnd, retval, &gsi); >+ if (tsan_func_exit_call) >+ gsi_insert_after (&gsi, tsan_func_exit_call, GSI_NEW_STMT); > ret = gimple_build_return (retval); > gsi_insert_after (&gsi, ret, GSI_NEW_STMT); > } >@@ -1792,6 +1811,8 @@ execute_split_functions (void) >/* Compute local info about basic blocks and determine function >size/time. */ > bb_info_vec.safe_grow_cleared (last_basic_block_for_fn (cfun) + 1); > memset (&best_split_point, 0, sizeof (best_split_point)); >+ basic_block return_bb = find_return_bb (); >+ int tsan_exit_found = -1; > FOR_EACH_BB_FN (bb, cfun) > { > int time = 0; >@@ -1818,16 +1839,37 @@ execute_split_functions (void) > freq, this_size, this_time); > print_gimple_stmt (dump_file, stmt, 0, 0); > } >+ >+ if ((flag_sanitize & SANITIZE_THREAD) >+ && is_gimple_call (stmt) >+ && gimple_call_internal_p (stmt) >+ && gimple_call_internal_fn (stmt) == IFN_TSAN_FUNC_EXIT) >+ { >+ /* We handle TSAN_FUNC_EXIT for splitting either in the >+ return_bb, or in its immediate predecessors. */ >+ if ((bb != return_bb && !find_edge (bb, return_bb)) >+ || (tsan_exit_found != -1 >+ && tsan_exit_found != (bb != return_bb))) >+ { >+ if (dump_file) >+ fprintf (dump_file, "Not splitting: TSAN_FUNC_EXIT" >+ " in unexpected basic block.\n"); >+ BITMAP_FREE (forbidden_dominators); >+ bb_info_vec.release (); >+ return 0; >+ } >+ tsan_exit_found = bb != return_bb; >+ } > } > overall_time += time; > overall_size += size; > bb_info_vec[bb->index].time = time; > bb_info_vec[bb->index].size = size; > } >- find_split_points (overall_time, overall_size); >+ find_split_points (return_bb, overall_time, overall_size); > if (best_split_point.split_bbs) > { >- split_function (&best_split_point); >+ split_function (return_bb, &best_split_point, tsan_exit_found == >1); > BITMAP_FREE (best_split_point.ssa_names_to_pass); > BITMAP_FREE (best_split_point.split_bbs); > todo = TODO_update_ssa | TODO_cleanup_cfg; >--- gcc/testsuite/c-c++-common/tsan/pr65400-1.c.jj 2015-03-18 >18:33:27.478940014 +0100 >+++ gcc/testsuite/c-c++-common/tsan/pr65400-1.c 2015-03-18 >18:43:49.235836436 +0100 >@@ -0,0 +1,85 @@ >+/* PR sanitizer/65400 */ >+/* { dg-shouldfail "tsan" } */ >+/* { dg-additional-options "-fno-omit-frame-pointer -ldl" } */ >+/* { dg-additional-sources pr65400-2.c } */ >+ >+#include <pthread.h> >+#include "tsan_barrier.h" >+ >+static pthread_barrier_t barrier; >+int v; >+int q; >+int o; >+extern void baz4 (int *); >+ >+__attribute__((noinline, noclone)) int >+bar (int x) >+{ >+ q += x; >+ return x; >+} >+ >+void >+foo (int *x) >+{ >+ if (__builtin_expect (x == 0, 1)) >+ return; >+ bar (bar (bar (bar (*x)))); >+} >+ >+__attribute__((noinline, noclone)) void >+baz1 (int *x) >+{ >+ foo (x); >+} >+ >+__attribute__((noinline, noclone)) void >+baz2 (int **x) >+{ >+ foo (*x); >+} >+ >+__attribute__((noinline, noclone)) void >+baz3 (void) >+{ >+ barrier_wait (&barrier); >+ v++; >+} >+ >+__attribute__((noinline, noclone)) void >+baz5 (void) >+{ >+ int i; >+ o = 1; >+ baz1 (&o); >+ int *p = &o; >+ baz2 (&p); >+ for (i = 0; i < 128; i++) >+ baz4 (&o); >+ if (q != 130 * 4) >+ __builtin_abort (); >+ baz3 (); >+} >+ >+__attribute__((noinline, noclone)) void * >+tf (void *arg) >+{ >+ (void) arg; >+ baz5 (); >+ return NULL; >+} >+ >+int >+main () >+{ >+ pthread_t th; >+ barrier_init (&barrier, 2); >+ if (pthread_create (&th, NULL, tf, NULL)) >+ return 0; >+ v++; >+ barrier_wait (&barrier); >+ pthread_join (th, NULL); >+ return 0; >+} >+ >+/* { dg-output "WARNING: ThreadSanitizer: data race.*#2 _?tf" } */ >--- gcc/testsuite/c-c++-common/tsan/pr65400-2.c.jj 2015-03-18 >18:40:07.855433878 +0100 >+++ gcc/testsuite/c-c++-common/tsan/pr65400-2.c 2015-03-18 >18:40:03.862498763 +0100 >@@ -0,0 +1,10 @@ >+/* PR sanitizer/65400 */ >+/* { dg-do compile } */ >+ >+extern void foo (int *); >+ >+void >+baz4 (int *p) >+{ >+ foo (p); >+} > > Jakub
--- gcc/ipa-split.c.jj 2015-03-17 18:10:11.000000000 +0100 +++ gcc/ipa-split.c 2015-03-18 17:12:45.017773858 +0100 @@ -769,7 +769,8 @@ consider_split (struct split_point *curr of the form: <retval> = tmp_var; return <retval> - but return_bb can not be more complex than this. + but return_bb can not be more complex than this (except for + -fsanitize=thread we allow TSAN_FUNC_EXIT () internal call in there). If nothing is found, return the exit block. When there are multiple RETURN statement, chose one with return value, @@ -814,6 +815,13 @@ find_return_bb (void) found_return = true; retval = gimple_return_retval (return_stmt); } + /* For -fsanitize=thread, allow also TSAN_FUNC_EXIT () in the return + bb. */ + else if ((flag_sanitize & SANITIZE_THREAD) + && is_gimple_call (stmt) + && gimple_call_internal_p (stmt) + && gimple_call_internal_fn (stmt) == IFN_TSAN_FUNC_EXIT) + ; else break; } @@ -1074,12 +1082,11 @@ typedef struct the component used by consider_split. */ static void -find_split_points (int overall_time, int overall_size) +find_split_points (basic_block return_bb, int overall_time, int overall_size) { stack_entry first; vec<stack_entry> stack = vNULL; basic_block bb; - basic_block return_bb = find_return_bb (); struct split_point current; current.header_time = overall_time; @@ -1236,19 +1243,20 @@ insert_bndret_call_after (tree retbnd, t gimple_call_set_lhs (bndret, retbnd); gsi_insert_after (gsi, bndret, GSI_CONTINUE_LINKING); } + /* Split function at SPLIT_POINT. */ static void -split_function (struct split_point *split_point) +split_function (basic_block return_bb, struct split_point *split_point, + bool add_tsan_func_exit) { vec<tree> args_to_pass = vNULL; bitmap args_to_skip; tree parm; int num = 0; cgraph_node *node, *cur_node = cgraph_node::get (current_function_decl); - basic_block return_bb = find_return_bb (); basic_block call_bb; - gcall *call; + gcall *call, *tsan_func_exit_call = NULL; edge e; edge_iterator ei; tree retval = NULL, real_retval = NULL, retbnd = NULL; @@ -1534,11 +1542,18 @@ split_function (struct split_point *spli || DECL_BY_REFERENCE (DECL_RESULT (current_function_decl)))) gimple_call_set_return_slot_opt (call, true); + if (add_tsan_func_exit) + tsan_func_exit_call = gimple_build_call_internal (IFN_TSAN_FUNC_EXIT, 0); + /* Update return value. This is bit tricky. When we do not return, do nothing. When we return we might need to update return_bb or produce a new return statement. */ if (!split_part_return_p) - gsi_insert_after (&gsi, call, GSI_NEW_STMT); + { + gsi_insert_after (&gsi, call, GSI_NEW_STMT); + if (tsan_func_exit_call) + gsi_insert_after (&gsi, tsan_func_exit_call, GSI_NEW_STMT); + } else { e = make_edge (call_bb, return_bb, @@ -1642,6 +1657,8 @@ split_function (struct split_point *spli } else gsi_insert_after (&gsi, call, GSI_NEW_STMT); + if (tsan_func_exit_call) + gsi_insert_after (&gsi, tsan_func_exit_call, GSI_NEW_STMT); } /* We don't use return block (there is either no return in function or multiple of them). So create new basic block with return statement. @@ -1684,6 +1701,8 @@ split_function (struct split_point *spli /* Build bndret call to obtain returned bounds. */ if (retbnd) insert_bndret_call_after (retbnd, retval, &gsi); + if (tsan_func_exit_call) + gsi_insert_after (&gsi, tsan_func_exit_call, GSI_NEW_STMT); ret = gimple_build_return (retval); gsi_insert_after (&gsi, ret, GSI_NEW_STMT); } @@ -1792,6 +1811,8 @@ execute_split_functions (void) /* Compute local info about basic blocks and determine function size/time. */ bb_info_vec.safe_grow_cleared (last_basic_block_for_fn (cfun) + 1); memset (&best_split_point, 0, sizeof (best_split_point)); + basic_block return_bb = find_return_bb (); + int tsan_exit_found = -1; FOR_EACH_BB_FN (bb, cfun) { int time = 0; @@ -1818,16 +1839,37 @@ execute_split_functions (void) freq, this_size, this_time); print_gimple_stmt (dump_file, stmt, 0, 0); } + + if ((flag_sanitize & SANITIZE_THREAD) + && is_gimple_call (stmt) + && gimple_call_internal_p (stmt) + && gimple_call_internal_fn (stmt) == IFN_TSAN_FUNC_EXIT) + { + /* We handle TSAN_FUNC_EXIT for splitting either in the + return_bb, or in its immediate predecessors. */ + if ((bb != return_bb && !find_edge (bb, return_bb)) + || (tsan_exit_found != -1 + && tsan_exit_found != (bb != return_bb))) + { + if (dump_file) + fprintf (dump_file, "Not splitting: TSAN_FUNC_EXIT" + " in unexpected basic block.\n"); + BITMAP_FREE (forbidden_dominators); + bb_info_vec.release (); + return 0; + } + tsan_exit_found = bb != return_bb; + } } overall_time += time; overall_size += size; bb_info_vec[bb->index].time = time; bb_info_vec[bb->index].size = size; } - find_split_points (overall_time, overall_size); + find_split_points (return_bb, overall_time, overall_size); if (best_split_point.split_bbs) { - split_function (&best_split_point); + split_function (return_bb, &best_split_point, tsan_exit_found == 1); BITMAP_FREE (best_split_point.ssa_names_to_pass); BITMAP_FREE (best_split_point.split_bbs); todo = TODO_update_ssa | TODO_cleanup_cfg; --- gcc/testsuite/c-c++-common/tsan/pr65400-1.c.jj 2015-03-18 18:33:27.478940014 +0100 +++ gcc/testsuite/c-c++-common/tsan/pr65400-1.c 2015-03-18 18:43:49.235836436 +0100 @@ -0,0 +1,85 @@ +/* PR sanitizer/65400 */ +/* { dg-shouldfail "tsan" } */ +/* { dg-additional-options "-fno-omit-frame-pointer -ldl" } */ +/* { dg-additional-sources pr65400-2.c } */ + +#include <pthread.h> +#include "tsan_barrier.h" + +static pthread_barrier_t barrier; +int v; +int q; +int o; +extern void baz4 (int *); + +__attribute__((noinline, noclone)) int +bar (int x) +{ + q += x; + return x; +} + +void +foo (int *x) +{ + if (__builtin_expect (x == 0, 1)) + return; + bar (bar (bar (bar (*x)))); +} + +__attribute__((noinline, noclone)) void +baz1 (int *x) +{ + foo (x); +} + +__attribute__((noinline, noclone)) void +baz2 (int **x) +{ + foo (*x); +} + +__attribute__((noinline, noclone)) void +baz3 (void) +{ + barrier_wait (&barrier); + v++; +} + +__attribute__((noinline, noclone)) void +baz5 (void) +{ + int i; + o = 1; + baz1 (&o); + int *p = &o; + baz2 (&p); + for (i = 0; i < 128; i++) + baz4 (&o); + if (q != 130 * 4) + __builtin_abort (); + baz3 (); +} + +__attribute__((noinline, noclone)) void * +tf (void *arg) +{ + (void) arg; + baz5 (); + return NULL; +} + +int +main () +{ + pthread_t th; + barrier_init (&barrier, 2); + if (pthread_create (&th, NULL, tf, NULL)) + return 0; + v++; + barrier_wait (&barrier); + pthread_join (th, NULL); + return 0; +} + +/* { dg-output "WARNING: ThreadSanitizer: data race.*#2 _?tf" } */ --- gcc/testsuite/c-c++-common/tsan/pr65400-2.c.jj 2015-03-18 18:40:07.855433878 +0100 +++ gcc/testsuite/c-c++-common/tsan/pr65400-2.c 2015-03-18 18:40:03.862498763 +0100 @@ -0,0 +1,10 @@ +/* PR sanitizer/65400 */ +/* { dg-do compile } */ + +extern void foo (int *); + +void +baz4 (int *p) +{ + foo (p); +}