|
|
Created:
13 years ago by dvyukov Modified:
12 years, 11 months ago CC:
kcc1, gcc-patches_gcc.gnu.org Base URL:
svn://gcc.gnu.org/svn/gcc/branches/google/main/ Visibility:
Public. |
Patch Set 1 #
Total comments: 49
Patch Set 2 : fixes after the review #
Total comments: 3
Patch Set 3 : fix review comments #Patch Set 4 : fix crashes #Patch Set 5 : remove commented out code #
Total comments: 1
Patch Set 6 : use cgraph_node_for_asm + add per-translation-unit constructors + synch repo #Patch Set 7 : Add ChangeLog #Patch Set 8 : Delete garbage from ChangeLog #
MessagesTotal messages: 42
The patch is for google/main branch. ThreadSanitizer is a data race detector for C/C++ programs. http://code.google.com/p/data-race-test/wiki/ThreadSanitizer The tool consists of two parts: instrumentation module (this file) and a run-time library. The instrumentation module mainintains shadow call stacks and intercepts interesting memory accesses. The instrumentation is enabled with -ftsan flag. Instrumentation for shadow stack maintainance is as follows: void somefunc () { __tsan_shadow_stack [-1] = __builtin_return_address (0); __tsan_shadow_stack++; // function body __tsan_shadow_stack--; } Interception for memory access interception is as follows: *addr = 1; __tsan_handle_mop (addr, flags); where flags are (is_sblock | (is_store << 1) | ((sizeof (*addr) - 1) << 2). is_sblock is used merely for optimization purposes and can always be set to 1, see comments in instrument_mops function. Ignore files can be used to selectively non instrument some functions. Ignore file is specified with -ftsan-ignore=filename flag. There are 3 types of ignores: (1) do not instrument memory accesses in the function, (2) do not create sblocks in the function and (3) recursively ignore memory accesses in the function. That last ignore type requires additional instrumentation of the form: void somefunc () { __tsan_thread_ignore++; // function body __tsan_thread_ignore--; } The run-time library provides __tsan_handle_mop function, definitions of __tsan_shadow_stack and __tsan_thread_ignore variables, and intercepts synchronization related functions. 2011-10-28 Dmitriy Vyukov <dvyukov@google.com> * gcc/doc/invoke.texi: * gcc/tree-tsan.c (enum tsan_ignore_e): (enum bb_state_e): (struct bb_data_t): (struct mop_desc_t): (struct tsan_ignore_desc_t): (lookup_name): (shadow_stack_def): (thread_ignore_def): (rtl_mop_def): (ignore_append): (ignore_match): (ignore_load): (tsan_ignore): (decl_name): (build_stack_op): (build_rec_ignore_op): (build_stack_assign): (instr_mop): (instr_vptr_store): (instr_func): (set_location): (is_dtor_vptr_store): (is_vtbl_read): (is_load_of_const): (handle_expr): (handle_gimple): (instrument_bblock): (instrument_mops): (instrument_function): (tsan_pass): (tsan_gate): * gcc/tree-pass.h: * gcc/testsuite/gcc.dg/tsan-ignore.ignore: * gcc/testsuite/gcc.dg/tsan.h (__tsan_init): (__tsan_expect_mop): (__tsan_handle_mop): * gcc/testsuite/gcc.dg/tsan-ignore.c (foo): (int bar): (int baz): (int bla): (int xxx): (main): * gcc/testsuite/gcc.dg/tsan-ignore.h (in_tsan_ignore_header): * gcc/testsuite/gcc.dg/tsan-stack.c (foobar): * gcc/testsuite/gcc.dg/tsan-mop.c: * gcc/common.opt: * gcc/Makefile.in: * gcc/passes.c: Index: gcc/doc/invoke.texi =================================================================== --- gcc/doc/invoke.texi (revision 180522) +++ gcc/doc/invoke.texi (working copy) @@ -308,6 +308,7 @@ -fdump-tree-ssa@r{[}-@var{n}@r{]} -fdump-tree-pre@r{[}-@var{n}@r{]} @gol -fdump-tree-ccp@r{[}-@var{n}@r{]} -fdump-tree-dce@r{[}-@var{n}@r{]} @gol -fdump-tree-gimple@r{[}-raw@r{]} -fdump-tree-mudflap@r{[}-@var{n}@r{]} @gol +-fdump-tree-tsan@r{[}-@var{n}@r{]} @gol -fdump-tree-dom@r{[}-@var{n}@r{]} @gol -fdump-tree-dse@r{[}-@var{n}@r{]} @gol -fdump-tree-phiprop@r{[}-@var{n}@r{]} @gol @@ -381,8 +382,8 @@ -floop-parallelize-all -flto -flto-compression-level @gol -flto-partition=@var{alg} -flto-report -fmerge-all-constants @gol -fmerge-constants -fmodulo-sched -fmodulo-sched-allow-regmoves @gol --fmove-loop-invariants fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol --fno-default-inline @gol +-fmove-loop-invariants -fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol +-ftsan -ftsan-ignore -fno-default-inline @gol -fno-defer-pop -fno-function-cse -fno-guess-branch-probability @gol -fno-inline -fno-math-errno -fno-peephole -fno-peephole2 @gol -fno-sched-interblock -fno-sched-spec -fno-signed-zeros @gol @@ -5896,6 +5897,11 @@ Dump each function after adding mudflap instrumentation. The file name is made by appending @file{.mudflap} to the source file name. +@item tsan +@opindex fdump-tree-tsan +Dump each function after adding ThreadSanitizer instrumentation. The file name is +made by appending @file{.tsan} to the source file name. + @item sra @opindex fdump-tree-sra Dump each function after performing scalar replacement of aggregates. The @@ -6674,6 +6680,12 @@ some protection against outright memory corrupting writes, but allows erroneously read data to propagate within a program. +@item -ftsan -ftsan-ignore +@opindex ftsan +@opindex ftsan-ignore +Add ThreadSanitizer instrumentation. Use @option{-ftsan-ignore} to specify +an ignore file. Refer to http://go/tsan for details. + @item -fthread-jumps @opindex fthread-jumps Perform optimizations where we check to see if a jump branches to a Index: gcc/tree-tsan.c =================================================================== --- gcc/tree-tsan.c (revision 0) +++ gcc/tree-tsan.c (revision 0) @@ -0,0 +1,1204 @@ +/* ThreadSanitizer instrumentation pass. + http://code.google.com/p/data-race-test + Copyright (C) 2011 + Free Software Foundation, Inc. + Contributed by Dmitry Vyukov <dvyukov@google.com> + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "intl.h" +#include "tm.h" +#include "basic-block.h" +#include "gimple.h" +#include "function.h" +#include "tree-flow.h" +#include "tree-pass.h" +#include "cfghooks.h" +#include "langhooks.h" +#include "output.h" +#include "options.h" + +/* The file can be compiled either as compiler pass or plugin. */ +#ifdef GCC_PLG +# include "c-common.h" +#else +# include "c-family/c-common.h" +#endif + +#include "diagnostic.h" + +#include <stdlib.h> +#include <stdio.h> + +/* ThreadSanitizer is a data race detector for C/C++ programs. + http://code.google.com/p/data-race-test/wiki/ThreadSanitizer + + The tool consists of two parts: + instrumentation module (this file) and a run-time library. + The instrumentation module mainintains shadow call stacks + and intercepts interesting memory accesses. + The instrumentation is enabled with -ftsan flag. + + Instrumentation for shadow stack maintainance is as follows: + void somefunc () + { + __tsan_shadow_stack [-1] = __builtin_return_address (0); + __tsan_shadow_stack++; + // function body + __tsan_shadow_stack--; + } + + Interception for memory access interception is as follows: + *addr = 1; + __tsan_handle_mop (addr, flags); + where flags are (is_sblock | (is_store << 1) | ((sizeof (*addr) - 1) << 2). + is_sblock is used merely for optimization purposes and can always + be set to 1, see comments in instrument_mops function. + + Ignore files can be used to selectively non instrument some functions. + Ignore file is specified with -ftsan-ignore=filename flag. + There are 3 types of ignores: (1) do not instrument memory accesses + in the function, (2) do not create sblocks in the function + and (3) recursively ignore memory accesses in the function. + That last ignore type requires additional instrumentation of the form: + void somefunc () + { + __tsan_thread_ignore++; + // function body + __tsan_thread_ignore--; + } + + The run-time library provides __tsan_handle_mop function, + definitions of __tsan_shadow_stack and __tsan_thread_ignore variables, + and intercepts synchronization related functions. */ + +#define RTL_IGNORE "__tsan_thread_ignore" +#define RTL_STACK "__tsan_shadow_stack" +#define RTL_MOP "__tsan_handle_mop" +#define RTL_PERFIX "__tsan_" +#define MAX_MOP_BYTES 16 +#define SBLOCK_SIZE 5 + +enum tsan_ignore_e +{ + tsan_ignore_none = 1 << 0, /* Do not ignore. */ + tsan_ignore_func = 1 << 1, /* Completely ignore the whole func. */ + tsan_ignore_mop = 1 << 2, /* Do not instrument memory accesses. */ + tsan_ignore_rec = 1 << 3, /* Do not instrument memory accesses recursively. */ + tsan_ignore_hist = 1 << 4 /* Do not create superblocks. */ +}; + +/* Basic block state during CFG traversal. */ +enum bb_state_e +{ + bb_not_visited, + bb_candidate, + bb_visited +}; + +/* Info associated with each basic block. + Used to determine super-blocks (see instrument_mops ()). */ +struct bb_data_t +{ + enum bb_state_e state; + int has_sb; + const char *sb_file; + int sb_line_min; + int sb_line_max; +}; + +/* Memory access descriptor. */ +struct mop_desc_t +{ + int is_call; + gimple_stmt_iterator gsi; + tree expr; + tree dtor_vptr_expr; + int is_store; +}; + +struct tsan_ignore_desc_t +{ + struct tsan_ignore_desc_t *next; + enum tsan_ignore_e type; + char *name; +}; + +/* Number of instrumented memory accesses in the current function. */ +static int func_mops; +/* Number of function calls in the current function. */ +static int func_calls; +/* Ignore status for the current function (see tsan_ignore_e). */ +static enum tsan_ignore_e func_ignore; + +static int ignore_init = 0; +static struct tsan_ignore_desc_t *ignore_head; + +typedef struct mop_desc_t mop_desc_t; +DEF_VEC_O (mop_desc_t); +DEF_VEC_ALLOC_O (mop_desc_t, heap); +static VEC (mop_desc_t, heap) *mop_list; + +/* The function is not available in some modules. */ +tree __attribute__((weak)) +lookup_name (tree t) +{ + (void)t; + return NULL_TREE; +} + +/* Builds the following decl + extern __thread void **__tsan_shadow_stack; */ +static tree +shadow_stack_def (void) +{ + static tree def; + + if (def != NULL) + return def; + + /* Check if a user has defined it for testing */ + def = lookup_name (get_identifier (RTL_STACK)); + if (def != NULL) + return def; + + def = build_decl (UNKNOWN_LOCATION, VAR_DECL, + get_identifier (RTL_STACK), + build_pointer_type (ptr_type_node)); + TREE_STATIC (def) = 1; + TREE_PUBLIC (def) = 1; + DECL_EXTERNAL (def) = 1; + DECL_TLS_MODEL (def) = decl_default_tls_model (def); + TREE_USED (def) = 1; + TREE_THIS_VOLATILE (def) = 1; + SET_DECL_ASSEMBLER_NAME (def, get_identifier (RTL_STACK)); + return def; +} + +/* Builds the following decl + extern __thread int __tsan_thread_ignore; */ +static tree +thread_ignore_def (void) +{ + static tree def; + + if (def != NULL) + return def; + + /* Check if a user has defined it for testing */ + def = lookup_name (get_identifier (RTL_IGNORE)); + if (def != NULL) + return def; + + def = build_decl (UNKNOWN_LOCATION, VAR_DECL, + get_identifier (RTL_IGNORE), + integer_type_node); + TREE_STATIC (def) = 1; + TREE_PUBLIC (def) = 1; + DECL_EXTERNAL (def) = 1; + DECL_TLS_MODEL (def) = decl_default_tls_model (def); + TREE_USED (def) = 1; + TREE_THIS_VOLATILE (def) = 1; + SET_DECL_ASSEMBLER_NAME (def, get_identifier (RTL_IGNORE)); + return def; +} + +/* Builds the following decl + void __tsan_handle_mop (void *addr, unsigned flags); */ +static tree +rtl_mop_def (void) +{ + tree fn_type; + + static tree def; + + if (def != NULL) + return def; + + /* Check if a user has defined it for testing */ + def = lookup_name (get_identifier (RTL_MOP)); + if (def != NULL) + return def; + + fn_type = build_function_type_list (void_type_node, ptr_type_node, + integer_type_node , NULL_TREE); + def = build_fn_decl (RTL_MOP, fn_type); + TREE_NOTHROW (def) = 1; + DECL_ATTRIBUTES (def) = tree_cons (get_identifier ("leaf"), + NULL, DECL_ATTRIBUTES (def)); + DECL_ASSEMBLER_NAME (def); + return def; +} + +/* Adds new ignore definition to the global list */ +static void +ignore_append (enum tsan_ignore_e type, char *name) +{ + struct tsan_ignore_desc_t *desc; + + desc = (struct tsan_ignore_desc_t*)xmalloc (sizeof (*desc)); + desc->type = type; + desc->name = xstrdup (name); + desc->next = ignore_head; + ignore_head = desc; +} + +/* Checks as to whether identifier 'str' matches template 'templ'. + Templates can only contain '*', e.g. 'std*string*insert'. + Templates implicitly start and end with '*' + since they are matched against mangled names. */ +static int +ignore_match (char *templ, const char *str) +{ + char *tpos; + const char *spos; + + while (templ && templ [0]) + { + if (templ [0] == '*') + { + templ++; + continue; + } + if (str [0] == 0) + return 0; + tpos = strchr (templ, '*'); + if (tpos != NULL) + tpos [0] = 0; + spos = strstr (str, templ); + str = spos + strlen (templ); + templ = tpos; + if (tpos != NULL) + tpos [0] = '*'; + if (spos == NULL) + return 0; + } + return 1; +} + +/* Loads ignore definitions from the file specified by -ftsan-ignore=filename. + Ignore files have the following format: + +# This is a comment - ignored + +# The below line says to not instrument memory accesses +# in all functions that match 'std*string*insert' +fun:std*string*insert + +# The below line says to not instrument memory accesses +# in the function called 'foobar' *and* in all functions +# that it calls recursively +fun_r:foobar + +# The below line says to not create superblocks +# in the function called 'barbaz' +fun_hist:barbaz + +# Ignore all functions in the source file +src:atomic.c + +# Everything else is uninteresting for us (e.g. obj:) +*/ +static void +ignore_load (void) +{ + FILE *f; + char *line; + size_t linesz; + ssize_t sz; + char buf [PATH_MAX]; + + if (flag_tsan_ignore == NULL || flag_tsan_ignore [0] == 0) + return; + + f = fopen (flag_tsan_ignore, "r"); + if (f == NULL) + { + /* Try to open it relative to main_input_filename. */ + strncpy (buf, main_input_filename, sizeof (buf)); + buf [sizeof (buf) - 1] = 0; + line = strrchr (buf, '/'); + if (line != NULL) + { + line++; + strncpy (line, flag_tsan_ignore, sizeof (buf) - (line - buf)); + buf [sizeof (buf) - 1] = 0; + f = fopen (buf, "r"); + } + } + if (f == NULL) + { + printf ("failed to open ignore file '%s'\n", flag_tsan_ignore); + exit (1); + } + + line = 0; + linesz = 0; + while ((sz = getline (&line, &linesz, f)) != -1) + { + if (sz == 0) + continue; + /* strip line terminator */ + if (line [sz-1] == '\r' || line [sz-1] == '\n') + line [sz-1] = 0; + if (strncmp (line, "src:", sizeof ("src:")-1) == 0) + ignore_append (tsan_ignore_func, line + sizeof ("src:")-1); + else if (strncmp (line, "fun:", sizeof ("fun:")-1) == 0) + ignore_append (tsan_ignore_mop, line + sizeof ("fun:")-1); + else if (strncmp (line, "fun_r:", sizeof ("fun_r:")-1) == 0) + ignore_append (tsan_ignore_rec, line + sizeof ("fun_r:")-1); + else if (strncmp (line, "fun_hist:", sizeof ("fun_hist:")-1) == 0) + ignore_append (tsan_ignore_hist, line + sizeof ("fun_hist:")-1); + /* other lines are not interesting */ + } + + free (line); + fclose (f); +} + +/* Returns ignore status for the current function */ +static enum tsan_ignore_e +tsan_ignore (void) +{ + const char *func_name; + const char *src_name; + struct tsan_ignore_desc_t *desc; + + if (ignore_init == 0) + { + ignore_load (); + ignore_init = 1; + } + + src_name = expand_location(cfun->function_start_locus).file; + if (src_name == NULL) + src_name = ""; + + func_name = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (cfun->decl)); + /* Ignore all functions starting with __tsan_ - intended for testing */ + if (strncmp (func_name, RTL_PERFIX, sizeof (RTL_PERFIX) - 1) == 0) + return tsan_ignore_func; + + for (desc = ignore_head; desc; desc = desc->next) + { + if (desc->type == tsan_ignore_func) + { + if (ignore_match (desc->name, src_name)) + return desc->type; + } + else if (ignore_match (desc->name, func_name)) + return desc->type; + } + return tsan_ignore_none; +} + +static const char * +decl_name (tree decl) +{ + tree id; + const char *name; + + if (decl != 0 && DECL_P (decl)) + { + id = DECL_NAME (decl); + if (id != NULL) + { + name = IDENTIFIER_POINTER (id); + if (name != NULL) + return name; + } + } + return "<unknown>"; +} + +/* Builds either (__tsan_shadow_stack += 1) or (__tsan_shadow_stack -= 1) expression + depending on 'do_dec' parameter. Appends the result to seq. */ +static void +build_stack_op (gimple_seq *seq, bool do_dec) +{ + tree op_size; + double_int op_size_cst; + unsigned long long size_val; + unsigned long long size_valhi; + tree op_expr; + tree assign; + tree rtl_stack; + gimple_seq s; + + op_size = TYPE_SIZE (ptr_type_node); + op_size_cst = tree_to_double_int (op_size); + size_val = op_size_cst.low / BITS_PER_UNIT; + size_valhi = 0; + if (do_dec) + { + size_val = -size_val; + size_valhi = -1; + } + op_size = build_int_cst_wide (sizetype, size_val, size_valhi); + rtl_stack = shadow_stack_def (); + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, rtl_stack, op_size); + assign = build2 (MODIFY_EXPR, ptr_type_node, rtl_stack, op_expr); + s = NULL; + force_gimple_operand (assign, &s, true, NULL_TREE); + gimple_seq_add_seq (seq, s); +} + +/* Builds either (__tsan_thread_ignore += 1) or (__tsan_thread_ignore -= 1) expression + depending on op parameter. Stores the result in seq. */ +static void +build_rec_ignore_op (gimple_seq *seq, enum tree_code op) +{ + tree rec_expr; + gimple_seq rec_inc; + gimple rec_assign; + tree rtl_ignore; + + rtl_ignore = thread_ignore_def (); + rec_expr = build2 (op, integer_type_node, rtl_ignore, integer_one_node); + rec_inc = NULL; + rec_expr = force_gimple_operand (rec_expr, &rec_inc, true, NULL_TREE); + rec_assign = gimple_build_assign (rtl_ignore, rec_expr); + gimple_seq_add_seq (seq, rec_inc); + gimple_seq_add_stmt (seq, rec_assign); +} + +/* Build the following gimple sequence: + __tsan_shadow_stack [-1] = __builtin_return_address (0); + Stores the result in seq. */ +static void +build_stack_assign (gimple_seq *seq) +{ + tree pc_addr; + tree op_size; + tree op_expr; + tree stack_op; + tree assign; + tree rtl_retaddr; + + rtl_retaddr = implicit_built_in_decls [BUILT_IN_RETURN_ADDRESS]; + pc_addr = build_call_expr (rtl_retaddr, 1, integer_zero_node); + op_size = build_int_cst_wide (sizetype, -(POINTER_SIZE / BITS_PER_UNIT), -1); + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, + shadow_stack_def (), op_size); + stack_op = build1 (INDIRECT_REF, ptr_type_node, op_expr); + assign = build2 (MODIFY_EXPR, ptr_type_node, stack_op, pc_addr); + force_gimple_operand (assign, seq, true, NULL_TREE); +} + +/* Builds the following gimple sequence: + __tsan_handle_mop (&expr, (is_sblock | (is_store << 1) | ((sizeof (expr)-1) << 2) + The result is stored in gseq. */ +static void +instr_mop (tree expr, int is_store, int is_sblock, gimple_seq *gseq) +{ + tree addr_expr; + tree expr_type; + unsigned size; + unsigned flags; + tree flags_expr; + tree call_expr; + + gcc_assert (gseq != 0 && *gseq == 0); + gcc_assert (is_gimple_addressable (expr)); + + addr_expr = build_addr (expr, current_function_decl); + expr_type = TREE_TYPE (expr); + while (TREE_CODE (expr_type) == ARRAY_TYPE) + expr_type = TREE_TYPE (expr_type); + size = TREE_INT_CST_LOW (TYPE_SIZE (expr_type)); + size = size / BITS_PER_UNIT; + if (size > MAX_MOP_BYTES) + size = MAX_MOP_BYTES; + size -= 1; + flags = ((!!is_sblock << 0) + (!!is_store << 1) + (size << 2)); + flags_expr = build_int_cst (unsigned_type_node, flags); + call_expr = build_call_expr (rtl_mop_def (), 2, addr_expr, flags_expr); + force_gimple_operand (call_expr, gseq, true, 0); +} + +/* Builds the following gimple sequence: + int is_store = (expr != rhs); // the temp is not actually introduced + __tsan_handle_mop (&expr, (is_sblock | (is_store << 1) | ((sizeof (expr)-1) << 2) + The result is stored in gseq. */ +static void +instr_vptr_store (tree expr, tree rhs, int is_sblock, gimple_seq *gseq) +{ + tree expr_ptr; + tree addr_expr; + tree expr_type; + tree expr_size; + double_int size; + unsigned flags; + tree flags_expr; + gimple_seq flags_seq; + gimple collect; + tree is_store_expr; + + expr_ptr = build_addr (expr, current_function_decl); + addr_expr = force_gimple_operand (expr_ptr, gseq, true, NULL_TREE); + expr_type = TREE_TYPE (expr); + while (TREE_CODE (expr_type) == ARRAY_TYPE) + expr_type = TREE_TYPE (expr_type); + expr_size = TYPE_SIZE (expr_type); + size = tree_to_double_int (expr_size); + gcc_assert (size.high == 0 && size.low != 0); + if (size.low > 128) + size.low = 128; + size.low = (size.low / 8) - 1; + flags = ((!!is_sblock << 0) + (size.low << 2)); + flags_expr = build_int_cst (unsigned_type_node, flags); + is_store_expr = build2 (NE_EXPR, integer_type_node, + build1 (VIEW_CONVERT_EXPR, size_type_node, expr), + build1 (VIEW_CONVERT_EXPR, size_type_node, rhs)); + is_store_expr = build2 (LSHIFT_EXPR, integer_type_node, + is_store_expr, integer_one_node); + flags_expr = build2 (BIT_IOR_EXPR, integer_type_node, + is_store_expr, flags_expr); + flags_seq = 0; + flags_expr = force_gimple_operand (flags_expr, &flags_seq, true, NULL_TREE); + gimple_seq_add_seq (gseq, flags_seq); + collect = gimple_build_call ( + rtl_mop_def (), 2, addr_expr, flags_expr); + gimple_seq_add_stmt (gseq, collect); +} + +/* Builds gimple sequences that must be inserted at function entry (pre) + and before function exit (post). */ +static void +instr_func (gimple_seq *pre, gimple_seq *post) +{ + /* In this case we need no instrumentation for the function */ + if (func_calls == 0 && func_mops == 0) + return; + + if (func_ignore != tsan_ignore_rec) + { + build_stack_assign (pre); + build_stack_op (pre, false); + build_stack_op (post, true); + } + + if (func_ignore == tsan_ignore_rec && func_calls != 0) + { + build_rec_ignore_op (pre, PLUS_EXPR); + build_rec_ignore_op (post, MINUS_EXPR); + } +} + +/* Sets location for all gimples in the seq. */ +static void +set_location (gimple_seq seq, location_t loc) +{ + gimple_seq_node n; + + for (n = gimple_seq_first (seq); n != NULL; n = n->next) + gimple_set_location (n->stmt, loc); +} + +/* Check as to whether expr refers to a store to vptr. */ +static tree +is_dtor_vptr_store (gimple stmt, tree expr, int is_store) +{ + if (is_store == 1 + && TREE_CODE (expr) == COMPONENT_REF + && gimple_assign_single_p (stmt) + && strcmp (decl_name (cfun->decl), "__base_dtor ") == 0) + { + tree comp = expr->exp.operands [0]; + while (TREE_CODE (comp) == COMPONENT_REF) + comp = comp->exp.operands [0]; + if (TREE_CODE (comp) == INDIRECT_REF || TREE_CODE (comp) == MEM_REF) + { + comp = comp->exp.operands [0]; + if (TREE_CODE (comp) == SSA_NAME) + comp = SSA_NAME_VAR (comp); + if (strcmp (decl_name (comp), "this") == 0) + { + tree field = expr->exp.operands [1]; + if (TREE_CODE (field) == FIELD_DECL + && strncmp (decl_name (field), + "_vptr.", sizeof ("_vptr.") - 1) == 0) + return gimple_assign_rhs1 (stmt); + } + } + } + return 0; +} + +/* Checks as to whether expr refers to a read from vtlb. + Vtlbs are immutable, so don't bother to instrument them. */ +static int +is_vtbl_read (tree expr, int is_store) +{ + /* We may not instrument reads from vtbl, because the data is constant. + vtbl read is of the form: + gimple_assign <component_ref, D.2133, x->_vptr.X, NULL> + gimple_assign <indirect_ref, D.2134, *D.2133, NULL> + or: + gimple_assign <component_ref, D.2133, x->_vptr.X, NULL> + gimple_assign <pointer_plus_expr, D.2135, D.2133, 8> + gimple_assign <indirect_ref, D.2136, *D.2135, NULL> */ + + if (is_store == 0 + && TREE_CODE (expr) == INDIRECT_REF) + { + tree ref_target = expr->exp.operands [0]; + if (TREE_CODE (ref_target) == SSA_NAME) + { + gimple ref_stmt = ref_target->ssa_name.def_stmt; + if (gimple_code (ref_stmt) == GIMPLE_ASSIGN) + { + if (gimple_expr_code (ref_stmt) == POINTER_PLUS_EXPR) + { + tree tmp = ref_stmt->gsmem.op [1]; + if (TREE_CODE (tmp) == SSA_NAME + && gimple_code (tmp->ssa_name.def_stmt) == GIMPLE_ASSIGN) + ref_stmt = tmp->ssa_name.def_stmt; + } + if (gimple_expr_code (ref_stmt) == COMPONENT_REF + && gimple_assign_single_p (ref_stmt)) + { + tree comp_expr = ref_stmt->gsmem.op [1]; + tree field_expr = comp_expr->exp.operands [1]; + if (TREE_CODE (field_expr) == FIELD_DECL + && strncmp (decl_name (field_expr), + "_vptr.", sizeof ("_vptr.") - 1) == 0) + return 1; + } + } + } + } + + return 0; +} + +/* Checks as to whether expr refers to constant var/field/param. + Don't bother to instrument them. */ +static int +is_load_of_const (tree expr, int is_store) +{ + if (is_store == 0) + { + if (TREE_CODE (expr) == COMPONENT_REF) + expr = expr->exp.operands [1]; + if (TREE_CODE (expr) == VAR_DECL + || TREE_CODE (expr) == PARM_DECL + || TREE_CODE (expr) == FIELD_DECL) + { + if (TREE_READONLY (expr)) + return 1; + } + } + return 0; +} + +static void +handle_expr (gimple stmt, gimple_stmt_iterator gsi, + tree expr, int is_store, VEC (mop_desc_t, heap) **mop_list) +{ + enum tree_code tcode; + struct mop_desc_t mop; + unsigned fld_off; + unsigned fld_size; + + /* map SSA name to real name */ + if (TREE_CODE (expr) == SSA_NAME) + expr = SSA_NAME_VAR (expr); + + tcode = TREE_CODE (expr); + + /* Below are things we do NOT want to instrument. */ + if (func_ignore & (tsan_ignore_mop | tsan_ignore_rec)) + { + return; + } + else if (TREE_CODE_CLASS (tcode) == tcc_constant) + { + /* various constant literals */ + return; + } + else if (TREE_CODE_CLASS (tcode) == tcc_declaration + && DECL_ARTIFICIAL (expr)) + { + /* compiler-emitted artificial variables */ + return; + } + if (tcode == RESULT_DECL) + { + /* store to function result */ + return; + } + else if (tcode == VAR_DECL + && TREE_ADDRESSABLE (expr) == 0 + && TREE_STATIC (expr) == 0) + { + /* the var does not live in memory -> no possibility of races */ + return; + } + else if (TREE_CODE (TREE_TYPE (expr)) == RECORD_TYPE) + { + /* TODO (dvyukov): implement me */ + return; + } + else if (tcode == CONSTRUCTOR) + { + /* TODO (dvyukov): implement me */ + return; + } + else if (tcode == PARM_DECL) + { + /* TODO (dvyukov): implement me */ + return; + } + else if (is_load_of_const (expr, is_store)) + { + /* load of a const variable/parameter/field */ + return; + } + else if (is_vtbl_read (expr, is_store)) + { + /* vtbl read */ + return; + } + else if (tcode == COMPONENT_REF) + { + tree field = expr->exp.operands [1]; + if (TREE_CODE (field) == FIELD_DECL) + { + fld_off = field->field_decl.bit_offset->int_cst.int_cst.low; + fld_size = field->decl_common.size->int_cst.int_cst.low; + if (((fld_off % BITS_PER_UNIT) != 0) + || ((fld_size % BITS_PER_UNIT) != 0)) + { + /* As of now it crashes compilation. + TODO (dvyukov): handle bit-fields as if touching the whole field */ + return; + } + } + } + + /* TODO (dvyukov): handle other cases + (FIELD_DECL, MEM_REF, ARRAY_RANGE_REF, TARGET_MEM_REF, ADDR_EXPR) */ + if (tcode != ARRAY_REF + && tcode != VAR_DECL + && tcode != COMPONENT_REF + && tcode != INDIRECT_REF + && tcode != MEM_REF) + return; + + mop.is_call = 0; + mop.gsi = gsi; + mop.expr = expr; + mop.dtor_vptr_expr = is_dtor_vptr_store (stmt, expr, is_store); + mop.is_store = is_store; + VEC_safe_push (mop_desc_t, heap, *mop_list, &mop); +} + +static void +handle_gimple (gimple_stmt_iterator gsi, VEC (mop_desc_t, heap) **mop_list) +{ + unsigned i; + struct mop_desc_t mop; + gimple stmt; + enum gimple_code gcode; + location_t loc; + tree rhs; + tree lhs; + + stmt = gsi_stmt (gsi); + gcode = gimple_code (stmt); + if (gcode >= LAST_AND_UNUSED_GIMPLE_CODE) + return; + + loc = gimple_location (stmt); + + switch (gcode) + { + /* TODO (dvyukov): handle GIMPLE_COND (can it access memmory?) */ + case GIMPLE_CALL: + { + func_calls += 1; + /* Handle call arguments as loads */ + for (i = 0; i < gimple_call_num_args (stmt); i++) + { + rhs = gimple_call_arg (stmt, i); + handle_expr (stmt, gsi, rhs, 0, mop_list); + } + + memset (&mop, 0, sizeof (mop)); + mop.is_call = 1; + VEC_safe_push (mop_desc_t, heap, *mop_list, &mop); + + /* Handle assignment lhs as store */ + lhs = gimple_call_lhs (stmt); + if (lhs != 0) + handle_expr (stmt, gsi, lhs, 1, mop_list); + + break; + } + + case GIMPLE_ASSIGN: + { + /* Handle assignment lhs as store */ + lhs = gimple_assign_lhs (stmt); + handle_expr (stmt, gsi, lhs, 1, mop_list); + + /* Handle operands as loads */ + for (i = 1; i < gimple_num_ops (stmt); i++) + { + rhs = gimple_op (stmt, i); + handle_expr (stmt, gsi, rhs, 0, mop_list); + } + break; + } + + case GIMPLE_BIND: + { + gcc_assert (!"there should be no GIMPLE_BIND on this level"); + break; + } + + default: + break; + } +} + +/* Instruments single basic block. */ +static void +instrument_bblock (struct bb_data_t *bbd, basic_block bb) +{ + int ix; + int is_sblock; + gimple_stmt_iterator gsi; + struct mop_desc_t *mop; + gimple stmt; + location_t loc; + expanded_location eloc; + gimple_seq instr_seq; + + /* Iterate over all gimples and collect interesting mops into mop_list. */ + VEC_free (mop_desc_t, heap, mop_list); + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + handle_gimple (gsi, &mop_list); + } + + mop = 0; + for (ix = 0; VEC_iterate (mop_desc_t, mop_list, ix, mop); ix += 1) + { + if (mop->is_call != 0) + { + /* After a function call we must start a brand new sblock, + because the function can contain synchronization. */ + bbd->has_sb = 0; + continue; + } + + func_mops += 1; + stmt = gsi_stmt (mop->gsi); + loc = gimple_location (stmt); + eloc = expand_location (loc); + + /* Check as to whether we may not set sblock flag + for the access */ + is_sblock = (bbd->has_sb == 0 + || !(eloc.file != 0 + && bbd->sb_file != 0 + && strcmp (eloc.file, bbd->sb_file) == 0 + && eloc.line >= bbd->sb_line_min + && eloc.line <= bbd->sb_line_max)); + + if (func_ignore == tsan_ignore_hist) + is_sblock = 0; + + if (is_sblock) + { + /* Start new sblock with new source info. */ + bbd->has_sb = 1; + bbd->sb_file = eloc.file; + bbd->sb_line_min = eloc.line; + bbd->sb_line_max = eloc.line + SBLOCK_SIZE; + } + + instr_seq = 0; + if (mop->dtor_vptr_expr == 0) + instr_mop (mop->expr, mop->is_store, is_sblock, &instr_seq); + else + instr_vptr_store (mop->expr, mop->dtor_vptr_expr, is_sblock, &instr_seq); + gcc_assert (instr_seq != 0); + set_location (instr_seq, loc); + /* Instrumentation for assignment of a function result + must be inserted after the call. Instrumentation for + reads of function arguments must be inserted before the call. + That's because the call can contain synchronization. */ + if (is_gimple_call (stmt) && mop->is_store == 1) + gsi_insert_seq_after (&mop->gsi, instr_seq, GSI_NEW_STMT); + else + gsi_insert_seq_before (&mop->gsi, instr_seq, GSI_SAME_STMT); + } +} + +/* Instruments all interesting memory accesses in the function */ +static void +instrument_mops (void) +{ + int sb_line_min; + int sb_line_max; + int bb_cnt; + int eidx; + basic_block bb; + basic_block entry_bb; + basic_block cur_bb; + basic_block any_bb; + struct bb_data_t *pred; + struct bb_data_t *succ; + struct bb_data_t *bb_data; + struct bb_data_t *bbd; + edge entry_edge; + edge e; + + /* The function does breadth-first traversal of CFG. + BB is visited preferably if all its predecessors are visited. + Such order is required to properly mark super-blocks. + The idea behind super-blocks is as follows. + If several memory accesses happen within SBLOCK_SIZE source code lines + from each other, then we only mark the first access as SBLOCK. + This allows runtime library to memorize stack trace + only for the first access and do not memorize for others. + This significantly reduces memory consumption in exchange for slightly + imprecise stack traces for previous accesses. */ + + /* First, mark all blocks as not visited, and entry block as candidate. */ + bb_cnt = cfun->cfg->x_n_basic_blocks; + bb_data = (struct bb_data_t*) xcalloc (bb_cnt, sizeof (struct bb_data_t)); + entry_bb = ENTRY_BLOCK_PTR; + entry_edge = single_succ_edge (entry_bb); + entry_bb = entry_edge->dest; + bb = 0; + FOR_EACH_BB (bb) + { + bb_data [bb->index].state = (bb == entry_bb) ? bb_candidate : bb_not_visited; + } + + /* Until all blocks are visited. */ + for (; ; ) + { + cur_bb = 0; + any_bb = 0; + /* Look for a candidate with all visited predecessors. */ + FOR_EACH_BB (bb) + { + bbd = &bb_data [bb->index]; + if (bbd->state == bb_candidate) + { + cur_bb = bb; + any_bb = bb; + e = 0; + for (eidx = 0; VEC_iterate (edge, bb->preds, eidx, e); eidx++) + { + pred = &bb_data [e->src->index]; + if (pred->state != bb_visited) + { + cur_bb = 0; + break; + } + } + } + if (cur_bb != 0) + break; + } + /* All blocks are visited. */ + if (any_bb == 0) + break; + /* If no blocks with all visited predecessors, choose any candidate. + Must be a loop. */ + cur_bb = cur_bb ? cur_bb : any_bb; + bbd = &bb_data [cur_bb->index]; + gcc_assert (bbd->state == bb_candidate); + bbd->state = bb_visited; + + /* Iterate over all predecessors and merge their sblock info. */ + e = 0; + for (eidx = 0; VEC_iterate (edge, cur_bb->preds, eidx, e); eidx++) + { + pred = &bb_data [e->src->index]; + if ((pred->state != bb_visited) + || (pred->has_sb == 0) + || (pred == bbd)) + { + /* If there is a not visited predecessor, + or a predecessor with no active sblock info, + or a self-loop, then we will have to start + a brand new sblock on next memory access. */ + bbd->has_sb = 0; + break; + } + else if (bbd->has_sb == 0) + { + /* If it's a first predecessor, just copy the info. */ + bbd->has_sb = 1; + bbd->sb_file = pred->sb_file; + bbd->sb_line_min = pred->sb_line_min; + bbd->sb_line_max = pred->sb_line_max; + } + else + { + /* Otherwise, find the interception + between two sblock descriptors. */ + bbd->has_sb = 0; + if (bbd->sb_file != 0 && pred->sb_file != 0 + && strcmp (bbd->sb_file, pred->sb_file) == 0) + { + sb_line_min = MAX (bbd->sb_line_min, pred->sb_line_min); + sb_line_max = MIN (bbd->sb_line_max, pred->sb_line_max); + if (sb_line_min <= sb_line_max) + { + bbd->has_sb = 1; + bbd->sb_line_min = sb_line_min; + bbd->sb_line_max = sb_line_max; + } + } + /* No interception, have to start new sblock. */ + if (bbd->has_sb == 0) + break; + } + } + + /* Finally, instrument the block. */ + instrument_bblock (bbd, cur_bb); + + /* Mark all successors as candidates. */ + for (eidx = 0; VEC_iterate (edge, cur_bb->succs, eidx, e); eidx++) + { + succ = &bb_data [e->dest->index]; + if (succ->state == bb_not_visited) + succ->state = bb_candidate; + } + } +} + +/* Instruments function entry and exit, if necessary. */ +static void +instrument_function (void) +{ + location_t loc; + gimple_seq pre_func_seq; + gimple_seq post_func_seq; + basic_block entry_bb; + basic_block first_bb; + basic_block bb; + edge entry_edge; + gimple_stmt_iterator first_gsi; + gimple_stmt_iterator gsi; + gimple_stmt_iterator gsi2; + gimple first_stmt; + gimple stmt; + + pre_func_seq = 0; + post_func_seq = 0; + instr_func (&pre_func_seq, &post_func_seq); + + if (pre_func_seq != 0) + { + /* Insert new BB before the first BB. */ + entry_bb = ENTRY_BLOCK_PTR; + entry_edge = single_succ_edge (entry_bb); + first_bb = entry_edge->dest; + first_gsi = gsi_start_bb (first_bb); + if (!gsi_end_p (first_gsi)) + { + first_stmt = gsi_stmt (first_gsi); + loc = gimple_location (first_stmt); + set_location (pre_func_seq, loc); + } + entry_bb = split_edge (entry_edge); + gsi = gsi_start_bb (entry_bb); + gsi_insert_seq_after (&gsi, pre_func_seq, GSI_NEW_STMT); + } + + if (post_func_seq != 0) + { + /* Find all function exits. */ + FOR_EACH_BB (bb) + { + gsi2 = gsi_start_bb (bb); + for (; ; ) + { + gsi = gsi2; + if (gsi_end_p (gsi)) + break; + gsi_next (&gsi2); + + stmt = gsi_stmt (gsi); + loc = gimple_location (stmt); + + if (gimple_code (stmt) == GIMPLE_RETURN) + { + set_location (post_func_seq, loc); + gsi_insert_seq_before (&gsi, post_func_seq, GSI_SAME_STMT); + } + } + } + } +} + +static unsigned +tsan_pass (void) +{ + if (errorcount != 0 || sorrycount != 0) + return 0; + + func_ignore = tsan_ignore (); + if (func_ignore == tsan_ignore_func) + return 0; + + func_calls = 0; + func_mops = 0; + + instrument_mops (); + instrument_function (); + + return 0; +} + +static bool +tsan_gate (void) +{ + return flag_tsan != 0; +} + +struct gimple_opt_pass pass_tsan = {{ + GIMPLE_PASS, + "tsan", /* name */ + tsan_gate, /* gate */ + tsan_pass, /* execute */ + NULL, /* sub */ + NULL, /* next */ + 0, /* static_pass_number */ + TV_NONE, /* tv_id */ + PROP_trees | PROP_cfg, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + TODO_dump_cgraph | TODO_dump_func | TODO_verify_all + | TODO_update_ssa | TODO_update_address_taken /* todo_flags_finish */ +}}; + Property changes on: gcc/tree-tsan.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/tree-pass.h =================================================================== --- gcc/tree-pass.h (revision 180522) +++ gcc/tree-pass.h (working copy) @@ -352,6 +352,7 @@ extern struct gimple_opt_pass pass_mudflap_1; extern struct gimple_opt_pass pass_mudflap_2; +extern struct gimple_opt_pass pass_tsan; extern struct gimple_opt_pass pass_lower_cf; extern struct gimple_opt_pass pass_refactor_eh; extern struct gimple_opt_pass pass_lower_eh; Index: gcc/testsuite/gcc.dg/tsan-ignore.ignore =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) @@ -0,0 +1,7 @@ +#comment +fun:foo +fun:*bar +fun:baz* +fun:*bla* +fun:x*x +src:tsan-ignore.h Index: gcc/testsuite/gcc.dg/tsan.h =================================================================== --- gcc/testsuite/gcc.dg/tsan.h (revision 0) +++ gcc/testsuite/gcc.dg/tsan.h (revision 0) @@ -0,0 +1,87 @@ +/* Helper declarations and functions for ThreadSanitizer instrumentation (-ftsan) testing */ + +int printf (char *str, ...); +void exit (int); + +/* Variables referenced by the instrumentation */ +__thread void **__tsan_shadow_stack; +__thread int __tsan_thread_ignore; + +/* Local helper vars */ +__thread void *shadow_stack[1024]; +__thread int mop_expect; +__thread int mop_depth; +__thread void* mop_addr; +__thread unsigned long long mop_pc; +__thread unsigned mop_flags; + +/* Setups shadow stack var (not instrumented) */ +void __attribute__ ((constructor)) +__tsan_init (void) +{ + __tsan_shadow_stack = shadow_stack; +} + +/* Declare that we expect an instrumented memory access (not instrumented). + depth - stack depth of the mop (0 - main, 1 - func called from main and so on). + addr - memory access address. + is_store - store/load. + is_sblock - superblock flag of the access. + size - memory access size in bytes. */ +void +__tsan_expect_mop (int depth, void *addr, int is_store, int is_sblock, int size) +{ + if (mop_expect) + { + printf ("missed mop: addr=%p pc=%d\n", mop_addr, mop_pc); + exit (1); + } + + mop_expect = 1; + mop_depth = depth; + mop_addr = addr; + mop_pc = (unsigned long long)__builtin_return_address(0); + mop_flags = !!is_sblock | (!!is_store << 1) | ((size - 1) << 2); +} + +/* Memory access function (referenced by instrumentation, not instrumented). */ +void +__tsan_handle_mop (void *addr, unsigned flags) +{ + unsigned long long pc; + int depth; + + printf ("mop: addr=%p flags=%x called from %p/%p\n", addr, flags, __builtin_return_address(1), __tsan_shadow_stack[-2]); + if (mop_expect == 0) + return; + + /* Verify parameters with what we expect. */ + + if (addr != mop_addr) + { + printf ("incorrect mop addr: %p/%p\n", addr, mop_addr); + exit (1); + } + + pc = (unsigned long long)__builtin_return_address(0); + if (pc < mop_pc - 100 || pc > mop_pc + 100) + { + printf ("incorrect mop pc: %p/%p\n", (void*)pc, (void*)mop_pc); + exit (1); + } + + depth = __tsan_shadow_stack - shadow_stack - 1; + if (depth != mop_depth) + { + printf ("incorrect mop depth: %d/%d\n", depth, mop_depth); + exit (1); + } + + if (flags != mop_flags) + { + printf ("incorrect mop flags: %x/%x\n", flags, mop_flags); + exit (1); + } + + mop_expect = 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan.h ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-ignore.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) @@ -0,0 +1,49 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan -ftsan-ignore=tsan-ignore.ignore" } */ +#include "tsan.h" +#include "tsan-ignore.h" + +/* Check ignore file handling. */ + +int +foo (int *p) +{ + p [0] = 1; +} + +int bar (int *p) +{ + p [0] = 1; +} + +int baz (int *p) +{ + p [0] = 1; +} + +int bla (int *p) +{ + p [0] = 1; +} + +int xxx (int *p) +{ + p [0] = 1; +} + +int +main (void) +{ + int p, x; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p)); + /* All these functions must be ignored. */ + foo (&x); + bar (&x); + baz (&x); + bla (&x); + xxx (&x); + in_tsan_ignore_header (&x); + p = 0; + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-ignore.h =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) @@ -0,0 +1,5 @@ +int +in_tsan_ignore_header (int *p) +{ + p [0] = 1; +} Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.h ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-stack.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) @@ -0,0 +1,23 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan" } */ +#include "tsan.h" + +/* Check shadow stack maintance. */ + +int +foobar (int *p) +{ + __tsan_expect_mop(1, p, 1, 1, sizeof(*p)); + p[0] = 1; +} + +int +main (void) +{ + int p; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p)); + p = 0; + foobar (&p); + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-stack.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-mop.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) @@ -0,0 +1,29 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan" } */ +#include "tsan.h" + +/* Sanity check for memory accesses instrumentation. */ + +int +foobar (int *p) +{ + __tsan_expect_mop(1, p, 1, 1, sizeof(*p)); + p[0] = 1; + + __tsan_expect_mop(1, p, 1, 1, sizeof(*p)); + *p = 2; + + __tsan_expect_mop(1, (char*)p+3, 1, 1, 1); + *((char*)p+3) = 3; +} + +int +main (void) +{ + int p; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p)); + p = 0; + foobar (&p); + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-mop.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/common.opt =================================================================== --- gcc/common.opt (revision 180522) +++ gcc/common.opt (working copy) @@ -1547,6 +1547,14 @@ Common RejectNegative Report Var(flag_mudflap_ignore_reads) Ignore read operations when inserting mudflap instrumentation +ftsan +Common RejectNegative Report Var(flag_tsan) +Add ThreadSanitizer instrumentation + +ftsan-ignore= +Common RejectNegative Joined Var(flag_tsan_ignore) +-ftsan-ignore=filename ThreadSanitizer ignore file + fdce Common Var(flag_dce) Init(1) Optimization Use the RTL dead code elimination pass Index: gcc/Makefile.in =================================================================== --- gcc/Makefile.in (revision 180522) +++ gcc/Makefile.in (working copy) @@ -1494,6 +1494,7 @@ tree-streamer-out.o \ tree-tailcall.o \ tree-threadsafe-analyze.o \ + tree-tsan.o \ tree-vect-generic.o \ tree-vect-patterns.o \ tree-vect-data-refs.o \ @@ -2814,6 +2815,12 @@ $(C_TREE_H) $(C_COMMON_H) $(GIMPLE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) \ output.h langhooks.h tree-mudflap.h $(TM_H) coretypes.h \ $(GGC_H) gt-tree-mudflap.h $(TREE_PASS_H) $(DIAGNOSTIC_CORE_H) +tree-tsan.o : $(CONFIG_H) $(SYSTEM_H) $(TREE_H) $(TREE_INLINE_H) \ + $(GIMPLE_H) $(DIAGNOSTIC_H) langhooks.h \ + $(TM_H) coretypes.h $(TREE_DUMP_H) $(TREE_PASS_H) $(CGRAPH_H) $(GGC_H) \ + $(BASIC_BLOCK_H) $(FLAGS_H) $(FUNCTION_H) \ + $(TM_P_H) $(TREE_FLOW_H) $(DIAGNOSTIC_CORE_H) $(GIMPLE_H) tree-iterator.h \ + intl.h cfghooks.h output.h options.h c-family/c-common.h tree-pretty-print.o : tree-pretty-print.c $(CONFIG_H) $(SYSTEM_H) \ $(TREE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) $(TREE_FLOW_H) \ $(TM_H) coretypes.h tree-iterator.h $(SCEV_H) langhooks.h \ Index: gcc/passes.c =================================================================== --- gcc/passes.c (revision 180522) +++ gcc/passes.c (working copy) @@ -1420,6 +1420,7 @@ NEXT_PASS (pass_lower_resx); NEXT_PASS (pass_nrv); NEXT_PASS (pass_mudflap_2); + NEXT_PASS (pass_tsan); NEXT_PASS (pass_cleanup_cfg_post_optimizing); NEXT_PASS (pass_warn_function_noreturn); -- This patch is available for review at http://codereview.appspot.com/5303083
Sign in to reply to this message.
Dmitriy, I will review it next week. Do you have some runtime overhead data ? thanks, David On Fri, Oct 28, 2011 at 6:25 AM, Dmitriy Vyukov <dvyukov@google.com> wrote: > The patch is for google/main branch. > ThreadSanitizer is a data race detector for C/C++ programs. > http://code.google.com/p/data-race-test/wiki/ThreadSanitizer > > The tool consists of two parts: > instrumentation module (this file) and a run-time library. > The instrumentation module mainintains shadow call stacks > and intercepts interesting memory accesses. > The instrumentation is enabled with -ftsan flag. > > Instrumentation for shadow stack maintainance is as follows: > void somefunc () > { > __tsan_shadow_stack [-1] = __builtin_return_address (0); > __tsan_shadow_stack++; > // function body > __tsan_shadow_stack--; > } > > Interception for memory access interception is as follows: > *addr = 1; > __tsan_handle_mop (addr, flags); > where flags are (is_sblock | (is_store << 1) | ((sizeof (*addr) - 1) << 2). > is_sblock is used merely for optimization purposes and can always > be set to 1, see comments in instrument_mops function. > > Ignore files can be used to selectively non instrument some functions. > Ignore file is specified with -ftsan-ignore=filename flag. > There are 3 types of ignores: (1) do not instrument memory accesses > in the function, (2) do not create sblocks in the function > and (3) recursively ignore memory accesses in the function. > That last ignore type requires additional instrumentation of the form: > void somefunc () > { > __tsan_thread_ignore++; > // function body > __tsan_thread_ignore--; > } > > The run-time library provides __tsan_handle_mop function, > definitions of __tsan_shadow_stack and __tsan_thread_ignore variables, > and intercepts synchronization related functions. > > 2011-10-28 Dmitriy Vyukov <dvyukov@google.com> > > * gcc/doc/invoke.texi: > * gcc/tree-tsan.c (enum tsan_ignore_e): > (enum bb_state_e): > (struct bb_data_t): > (struct mop_desc_t): > (struct tsan_ignore_desc_t): > (lookup_name): > (shadow_stack_def): > (thread_ignore_def): > (rtl_mop_def): > (ignore_append): > (ignore_match): > (ignore_load): > (tsan_ignore): > (decl_name): > (build_stack_op): > (build_rec_ignore_op): > (build_stack_assign): > (instr_mop): > (instr_vptr_store): > (instr_func): > (set_location): > (is_dtor_vptr_store): > (is_vtbl_read): > (is_load_of_const): > (handle_expr): > (handle_gimple): > (instrument_bblock): > (instrument_mops): > (instrument_function): > (tsan_pass): > (tsan_gate): > * gcc/tree-pass.h: > * gcc/testsuite/gcc.dg/tsan-ignore.ignore: > * gcc/testsuite/gcc.dg/tsan.h (__tsan_init): > (__tsan_expect_mop): > (__tsan_handle_mop): > * gcc/testsuite/gcc.dg/tsan-ignore.c (foo): > (int bar): > (int baz): > (int bla): > (int xxx): > (main): > * gcc/testsuite/gcc.dg/tsan-ignore.h (in_tsan_ignore_header): > * gcc/testsuite/gcc.dg/tsan-stack.c (foobar): > * gcc/testsuite/gcc.dg/tsan-mop.c: > * gcc/common.opt: > * gcc/Makefile.in: > * gcc/passes.c: > > Index: gcc/doc/invoke.texi > =================================================================== > --- gcc/doc/invoke.texi (revision 180522) > +++ gcc/doc/invoke.texi (working copy) > @@ -308,6 +308,7 @@ > -fdump-tree-ssa@r{[}-@var{n}@r{]} -fdump-tree-pre@r{[}-@var{n}@r{]} @gol > -fdump-tree-ccp@r{[}-@var{n}@r{]} -fdump-tree-dce@r{[}-@var{n}@r{]} @gol > -fdump-tree-gimple@r{[}-raw@r{]} -fdump-tree-mudflap@r{[}-@var{n}@r{]} @gol > +-fdump-tree-tsan@r{[}-@var{n}@r{]} @gol > -fdump-tree-dom@r{[}-@var{n}@r{]} @gol > -fdump-tree-dse@r{[}-@var{n}@r{]} @gol > -fdump-tree-phiprop@r{[}-@var{n}@r{]} @gol > @@ -381,8 +382,8 @@ > -floop-parallelize-all -flto -flto-compression-level @gol > -flto-partition=@var{alg} -flto-report -fmerge-all-constants @gol > -fmerge-constants -fmodulo-sched -fmodulo-sched-allow-regmoves @gol > --fmove-loop-invariants fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol > --fno-default-inline @gol > +-fmove-loop-invariants -fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol > +-ftsan -ftsan-ignore -fno-default-inline @gol > -fno-defer-pop -fno-function-cse -fno-guess-branch-probability @gol > -fno-inline -fno-math-errno -fno-peephole -fno-peephole2 @gol > -fno-sched-interblock -fno-sched-spec -fno-signed-zeros @gol > @@ -5896,6 +5897,11 @@ > Dump each function after adding mudflap instrumentation. The file name is > made by appending @file{.mudflap} to the source file name. > > +@item tsan > +@opindex fdump-tree-tsan > +Dump each function after adding ThreadSanitizer instrumentation. The file name is > +made by appending @file{.tsan} to the source file name. > + > @item sra > @opindex fdump-tree-sra > Dump each function after performing scalar replacement of aggregates. The > @@ -6674,6 +6680,12 @@ > some protection against outright memory corrupting writes, but allows > erroneously read data to propagate within a program. > > +@item -ftsan -ftsan-ignore > +@opindex ftsan > +@opindex ftsan-ignore > +Add ThreadSanitizer instrumentation. Use @option{-ftsan-ignore} to specify > +an ignore file. Refer to http://go/tsan for details. > + > @item -fthread-jumps > @opindex fthread-jumps > Perform optimizations where we check to see if a jump branches to a > Index: gcc/tree-tsan.c > =================================================================== > --- gcc/tree-tsan.c (revision 0) > +++ gcc/tree-tsan.c (revision 0) > @@ -0,0 +1,1204 @@ > +/* ThreadSanitizer instrumentation pass. > + http://code.google.com/p/data-race-test > + Copyright (C) 2011 > + Free Software Foundation, Inc. > + Contributed by Dmitry Vyukov <dvyukov@google.com> > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify > +it under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 3, or (at your option) > +any later version. > + > +GCC is distributed in the hope that it will be useful, > +but WITHOUT ANY WARRANTY; without even the implied warranty of > +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +GNU General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#include "config.h" > +#include "system.h" > +#include "coretypes.h" > +#include "tree.h" > +#include "intl.h" > +#include "tm.h" > +#include "basic-block.h" > +#include "gimple.h" > +#include "function.h" > +#include "tree-flow.h" > +#include "tree-pass.h" > +#include "cfghooks.h" > +#include "langhooks.h" > +#include "output.h" > +#include "options.h" > + > +/* The file can be compiled either as compiler pass or plugin. */ > +#ifdef GCC_PLG > +# include "c-common.h" > +#else > +# include "c-family/c-common.h" > +#endif > + > +#include "diagnostic.h" > + > +#include <stdlib.h> > +#include <stdio.h> > + > +/* ThreadSanitizer is a data race detector for C/C++ programs. > + http://code.google.com/p/data-race-test/wiki/ThreadSanitizer > + > + The tool consists of two parts: > + instrumentation module (this file) and a run-time library. > + The instrumentation module mainintains shadow call stacks > + and intercepts interesting memory accesses. > + The instrumentation is enabled with -ftsan flag. > + > + Instrumentation for shadow stack maintainance is as follows: > + void somefunc () > + { > + __tsan_shadow_stack [-1] = __builtin_return_address (0); > + __tsan_shadow_stack++; > + // function body > + __tsan_shadow_stack--; > + } > + > + Interception for memory access interception is as follows: > + *addr = 1; > + __tsan_handle_mop (addr, flags); > + where flags are (is_sblock | (is_store << 1) | ((sizeof (*addr) - 1) << 2). > + is_sblock is used merely for optimization purposes and can always > + be set to 1, see comments in instrument_mops function. > + > + Ignore files can be used to selectively non instrument some functions. > + Ignore file is specified with -ftsan-ignore=filename flag. > + There are 3 types of ignores: (1) do not instrument memory accesses > + in the function, (2) do not create sblocks in the function > + and (3) recursively ignore memory accesses in the function. > + That last ignore type requires additional instrumentation of the form: > + void somefunc () > + { > + __tsan_thread_ignore++; > + // function body > + __tsan_thread_ignore--; > + } > + > + The run-time library provides __tsan_handle_mop function, > + definitions of __tsan_shadow_stack and __tsan_thread_ignore variables, > + and intercepts synchronization related functions. */ > + > +#define RTL_IGNORE "__tsan_thread_ignore" > +#define RTL_STACK "__tsan_shadow_stack" > +#define RTL_MOP "__tsan_handle_mop" > +#define RTL_PERFIX "__tsan_" > +#define MAX_MOP_BYTES 16 > +#define SBLOCK_SIZE 5 > + > +enum tsan_ignore_e > +{ > + tsan_ignore_none = 1 << 0, /* Do not ignore. */ > + tsan_ignore_func = 1 << 1, /* Completely ignore the whole func. */ > + tsan_ignore_mop = 1 << 2, /* Do not instrument memory accesses. */ > + tsan_ignore_rec = 1 << 3, /* Do not instrument memory accesses recursively. */ > + tsan_ignore_hist = 1 << 4 /* Do not create superblocks. */ > +}; > + > +/* Basic block state during CFG traversal. */ > +enum bb_state_e > +{ > + bb_not_visited, > + bb_candidate, > + bb_visited > +}; > + > +/* Info associated with each basic block. > + Used to determine super-blocks (see instrument_mops ()). */ > +struct bb_data_t > +{ > + enum bb_state_e state; > + int has_sb; > + const char *sb_file; > + int sb_line_min; > + int sb_line_max; > +}; > + > +/* Memory access descriptor. */ > +struct mop_desc_t > +{ > + int is_call; > + gimple_stmt_iterator gsi; > + tree expr; > + tree dtor_vptr_expr; > + int is_store; > +}; > + > +struct tsan_ignore_desc_t > +{ > + struct tsan_ignore_desc_t *next; > + enum tsan_ignore_e type; > + char *name; > +}; > + > +/* Number of instrumented memory accesses in the current function. */ > +static int func_mops; > +/* Number of function calls in the current function. */ > +static int func_calls; > +/* Ignore status for the current function (see tsan_ignore_e). */ > +static enum tsan_ignore_e func_ignore; > + > +static int ignore_init = 0; > +static struct tsan_ignore_desc_t *ignore_head; > + > +typedef struct mop_desc_t mop_desc_t; > +DEF_VEC_O (mop_desc_t); > +DEF_VEC_ALLOC_O (mop_desc_t, heap); > +static VEC (mop_desc_t, heap) *mop_list; > + > +/* The function is not available in some modules. */ > +tree __attribute__((weak)) > +lookup_name (tree t) > +{ > + (void)t; > + return NULL_TREE; > +} > + > +/* Builds the following decl > + extern __thread void **__tsan_shadow_stack; */ > +static tree > +shadow_stack_def (void) > +{ > + static tree def; > + > + if (def != NULL) > + return def; > + > + /* Check if a user has defined it for testing */ > + def = lookup_name (get_identifier (RTL_STACK)); > + if (def != NULL) > + return def; > + > + def = build_decl (UNKNOWN_LOCATION, VAR_DECL, > + get_identifier (RTL_STACK), > + build_pointer_type (ptr_type_node)); > + TREE_STATIC (def) = 1; > + TREE_PUBLIC (def) = 1; > + DECL_EXTERNAL (def) = 1; > + DECL_TLS_MODEL (def) = decl_default_tls_model (def); > + TREE_USED (def) = 1; > + TREE_THIS_VOLATILE (def) = 1; > + SET_DECL_ASSEMBLER_NAME (def, get_identifier (RTL_STACK)); > + return def; > +} > + > +/* Builds the following decl > + extern __thread int __tsan_thread_ignore; */ > +static tree > +thread_ignore_def (void) > +{ > + static tree def; > + > + if (def != NULL) > + return def; > + > + /* Check if a user has defined it for testing */ > + def = lookup_name (get_identifier (RTL_IGNORE)); > + if (def != NULL) > + return def; > + > + def = build_decl (UNKNOWN_LOCATION, VAR_DECL, > + get_identifier (RTL_IGNORE), > + integer_type_node); > + TREE_STATIC (def) = 1; > + TREE_PUBLIC (def) = 1; > + DECL_EXTERNAL (def) = 1; > + DECL_TLS_MODEL (def) = decl_default_tls_model (def); > + TREE_USED (def) = 1; > + TREE_THIS_VOLATILE (def) = 1; > + SET_DECL_ASSEMBLER_NAME (def, get_identifier (RTL_IGNORE)); > + return def; > +} > + > +/* Builds the following decl > + void __tsan_handle_mop (void *addr, unsigned flags); */ > +static tree > +rtl_mop_def (void) > +{ > + tree fn_type; > + > + static tree def; > + > + if (def != NULL) > + return def; > + > + /* Check if a user has defined it for testing */ > + def = lookup_name (get_identifier (RTL_MOP)); > + if (def != NULL) > + return def; > + > + fn_type = build_function_type_list (void_type_node, ptr_type_node, > + integer_type_node , NULL_TREE); > + def = build_fn_decl (RTL_MOP, fn_type); > + TREE_NOTHROW (def) = 1; > + DECL_ATTRIBUTES (def) = tree_cons (get_identifier ("leaf"), > + NULL, DECL_ATTRIBUTES (def)); > + DECL_ASSEMBLER_NAME (def); > + return def; > +} > + > +/* Adds new ignore definition to the global list */ > +static void > +ignore_append (enum tsan_ignore_e type, char *name) > +{ > + struct tsan_ignore_desc_t *desc; > + > + desc = (struct tsan_ignore_desc_t*)xmalloc (sizeof (*desc)); > + desc->type = type; > + desc->name = xstrdup (name); > + desc->next = ignore_head; > + ignore_head = desc; > +} > + > +/* Checks as to whether identifier 'str' matches template 'templ'. > + Templates can only contain '*', e.g. 'std*string*insert'. > + Templates implicitly start and end with '*' > + since they are matched against mangled names. */ > +static int > +ignore_match (char *templ, const char *str) > +{ > + char *tpos; > + const char *spos; > + > + while (templ && templ [0]) > + { > + if (templ [0] == '*') > + { > + templ++; > + continue; > + } > + if (str [0] == 0) > + return 0; > + tpos = strchr (templ, '*'); > + if (tpos != NULL) > + tpos [0] = 0; > + spos = strstr (str, templ); > + str = spos + strlen (templ); > + templ = tpos; > + if (tpos != NULL) > + tpos [0] = '*'; > + if (spos == NULL) > + return 0; > + } > + return 1; > +} > + > +/* Loads ignore definitions from the file specified by -ftsan-ignore=filename. > + Ignore files have the following format: > + > +# This is a comment - ignored > + > +# The below line says to not instrument memory accesses > +# in all functions that match 'std*string*insert' > +fun:std*string*insert > + > +# The below line says to not instrument memory accesses > +# in the function called 'foobar' *and* in all functions > +# that it calls recursively > +fun_r:foobar > + > +# The below line says to not create superblocks > +# in the function called 'barbaz' > +fun_hist:barbaz > + > +# Ignore all functions in the source file > +src:atomic.c > + > +# Everything else is uninteresting for us (e.g. obj:) > +*/ > +static void > +ignore_load (void) > +{ > + FILE *f; > + char *line; > + size_t linesz; > + ssize_t sz; > + char buf [PATH_MAX]; > + > + if (flag_tsan_ignore == NULL || flag_tsan_ignore [0] == 0) > + return; > + > + f = fopen (flag_tsan_ignore, "r"); > + if (f == NULL) > + { > + /* Try to open it relative to main_input_filename. */ > + strncpy (buf, main_input_filename, sizeof (buf)); > + buf [sizeof (buf) - 1] = 0; > + line = strrchr (buf, '/'); > + if (line != NULL) > + { > + line++; > + strncpy (line, flag_tsan_ignore, sizeof (buf) - (line - buf)); > + buf [sizeof (buf) - 1] = 0; > + f = fopen (buf, "r"); > + } > + } > + if (f == NULL) > + { > + printf ("failed to open ignore file '%s'\n", flag_tsan_ignore); > + exit (1); > + } > + > + line = 0; > + linesz = 0; > + while ((sz = getline (&line, &linesz, f)) != -1) > + { > + if (sz == 0) > + continue; > + /* strip line terminator */ > + if (line [sz-1] == '\r' || line [sz-1] == '\n') > + line [sz-1] = 0; > + if (strncmp (line, "src:", sizeof ("src:")-1) == 0) > + ignore_append (tsan_ignore_func, line + sizeof ("src:")-1); > + else if (strncmp (line, "fun:", sizeof ("fun:")-1) == 0) > + ignore_append (tsan_ignore_mop, line + sizeof ("fun:")-1); > + else if (strncmp (line, "fun_r:", sizeof ("fun_r:")-1) == 0) > + ignore_append (tsan_ignore_rec, line + sizeof ("fun_r:")-1); > + else if (strncmp (line, "fun_hist:", sizeof ("fun_hist:")-1) == 0) > + ignore_append (tsan_ignore_hist, line + sizeof ("fun_hist:")-1); > + /* other lines are not interesting */ > + } > + > + free (line); > + fclose (f); > +} > + > +/* Returns ignore status for the current function */ > +static enum tsan_ignore_e > +tsan_ignore (void) > +{ > + const char *func_name; > + const char *src_name; > + struct tsan_ignore_desc_t *desc; > + > + if (ignore_init == 0) > + { > + ignore_load (); > + ignore_init = 1; > + } > + > + src_name = expand_location(cfun->function_start_locus).file; > + if (src_name == NULL) > + src_name = ""; > + > + func_name = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (cfun->decl)); > + /* Ignore all functions starting with __tsan_ - intended for testing */ > + if (strncmp (func_name, RTL_PERFIX, sizeof (RTL_PERFIX) - 1) == 0) > + return tsan_ignore_func; > + > + for (desc = ignore_head; desc; desc = desc->next) > + { > + if (desc->type == tsan_ignore_func) > + { > + if (ignore_match (desc->name, src_name)) > + return desc->type; > + } > + else if (ignore_match (desc->name, func_name)) > + return desc->type; > + } > + return tsan_ignore_none; > +} > + > +static const char * > +decl_name (tree decl) > +{ > + tree id; > + const char *name; > + > + if (decl != 0 && DECL_P (decl)) > + { > + id = DECL_NAME (decl); > + if (id != NULL) > + { > + name = IDENTIFIER_POINTER (id); > + if (name != NULL) > + return name; > + } > + } > + return "<unknown>"; > +} > + > +/* Builds either (__tsan_shadow_stack += 1) or (__tsan_shadow_stack -= 1) expression > + depending on 'do_dec' parameter. Appends the result to seq. */ > +static void > +build_stack_op (gimple_seq *seq, bool do_dec) > +{ > + tree op_size; > + double_int op_size_cst; > + unsigned long long size_val; > + unsigned long long size_valhi; > + tree op_expr; > + tree assign; > + tree rtl_stack; > + gimple_seq s; > + > + op_size = TYPE_SIZE (ptr_type_node); > + op_size_cst = tree_to_double_int (op_size); > + size_val = op_size_cst.low / BITS_PER_UNIT; > + size_valhi = 0; > + if (do_dec) > + { > + size_val = -size_val; > + size_valhi = -1; > + } > + op_size = build_int_cst_wide (sizetype, size_val, size_valhi); > + rtl_stack = shadow_stack_def (); > + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, rtl_stack, op_size); > + assign = build2 (MODIFY_EXPR, ptr_type_node, rtl_stack, op_expr); > + s = NULL; > + force_gimple_operand (assign, &s, true, NULL_TREE); > + gimple_seq_add_seq (seq, s); > +} > + > +/* Builds either (__tsan_thread_ignore += 1) or (__tsan_thread_ignore -= 1) expression > + depending on op parameter. Stores the result in seq. */ > +static void > +build_rec_ignore_op (gimple_seq *seq, enum tree_code op) > +{ > + tree rec_expr; > + gimple_seq rec_inc; > + gimple rec_assign; > + tree rtl_ignore; > + > + rtl_ignore = thread_ignore_def (); > + rec_expr = build2 (op, integer_type_node, rtl_ignore, integer_one_node); > + rec_inc = NULL; > + rec_expr = force_gimple_operand (rec_expr, &rec_inc, true, NULL_TREE); > + rec_assign = gimple_build_assign (rtl_ignore, rec_expr); > + gimple_seq_add_seq (seq, rec_inc); > + gimple_seq_add_stmt (seq, rec_assign); > +} > + > +/* Build the following gimple sequence: > + __tsan_shadow_stack [-1] = __builtin_return_address (0); > + Stores the result in seq. */ > +static void > +build_stack_assign (gimple_seq *seq) > +{ > + tree pc_addr; > + tree op_size; > + tree op_expr; > + tree stack_op; > + tree assign; > + tree rtl_retaddr; > + > + rtl_retaddr = implicit_built_in_decls [BUILT_IN_RETURN_ADDRESS]; > + pc_addr = build_call_expr (rtl_retaddr, 1, integer_zero_node); > + op_size = build_int_cst_wide (sizetype, -(POINTER_SIZE / BITS_PER_UNIT), -1); > + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, > + shadow_stack_def (), op_size); > + stack_op = build1 (INDIRECT_REF, ptr_type_node, op_expr); > + assign = build2 (MODIFY_EXPR, ptr_type_node, stack_op, pc_addr); > + force_gimple_operand (assign, seq, true, NULL_TREE); > +} > + > +/* Builds the following gimple sequence: > + __tsan_handle_mop (&expr, (is_sblock | (is_store << 1) | ((sizeof (expr)-1) << 2) > + The result is stored in gseq. */ > +static void > +instr_mop (tree expr, int is_store, int is_sblock, gimple_seq *gseq) > +{ > + tree addr_expr; > + tree expr_type; > + unsigned size; > + unsigned flags; > + tree flags_expr; > + tree call_expr; > + > + gcc_assert (gseq != 0 && *gseq == 0); > + gcc_assert (is_gimple_addressable (expr)); > + > + addr_expr = build_addr (expr, current_function_decl); > + expr_type = TREE_TYPE (expr); > + while (TREE_CODE (expr_type) == ARRAY_TYPE) > + expr_type = TREE_TYPE (expr_type); > + size = TREE_INT_CST_LOW (TYPE_SIZE (expr_type)); > + size = size / BITS_PER_UNIT; > + if (size > MAX_MOP_BYTES) > + size = MAX_MOP_BYTES; > + size -= 1; > + flags = ((!!is_sblock << 0) + (!!is_store << 1) + (size << 2)); > + flags_expr = build_int_cst (unsigned_type_node, flags); > + call_expr = build_call_expr (rtl_mop_def (), 2, addr_expr, flags_expr); > + force_gimple_operand (call_expr, gseq, true, 0); > +} > + > +/* Builds the following gimple sequence: > + int is_store = (expr != rhs); // the temp is not actually introduced > + __tsan_handle_mop (&expr, (is_sblock | (is_store << 1) | ((sizeof (expr)-1) << 2) > + The result is stored in gseq. */ > +static void > +instr_vptr_store (tree expr, tree rhs, int is_sblock, gimple_seq *gseq) > +{ > + tree expr_ptr; > + tree addr_expr; > + tree expr_type; > + tree expr_size; > + double_int size; > + unsigned flags; > + tree flags_expr; > + gimple_seq flags_seq; > + gimple collect; > + tree is_store_expr; > + > + expr_ptr = build_addr (expr, current_function_decl); > + addr_expr = force_gimple_operand (expr_ptr, gseq, true, NULL_TREE); > + expr_type = TREE_TYPE (expr); > + while (TREE_CODE (expr_type) == ARRAY_TYPE) > + expr_type = TREE_TYPE (expr_type); > + expr_size = TYPE_SIZE (expr_type); > + size = tree_to_double_int (expr_size); > + gcc_assert (size.high == 0 && size.low != 0); > + if (size.low > 128) > + size.low = 128; > + size.low = (size.low / 8) - 1; > + flags = ((!!is_sblock << 0) + (size.low << 2)); > + flags_expr = build_int_cst (unsigned_type_node, flags); > + is_store_expr = build2 (NE_EXPR, integer_type_node, > + build1 (VIEW_CONVERT_EXPR, size_type_node, expr), > + build1 (VIEW_CONVERT_EXPR, size_type_node, rhs)); > + is_store_expr = build2 (LSHIFT_EXPR, integer_type_node, > + is_store_expr, integer_one_node); > + flags_expr = build2 (BIT_IOR_EXPR, integer_type_node, > + is_store_expr, flags_expr); > + flags_seq = 0; > + flags_expr = force_gimple_operand (flags_expr, &flags_seq, true, NULL_TREE); > + gimple_seq_add_seq (gseq, flags_seq); > + collect = gimple_build_call ( > + rtl_mop_def (), 2, addr_expr, flags_expr); > + gimple_seq_add_stmt (gseq, collect); > +} > + > +/* Builds gimple sequences that must be inserted at function entry (pre) > + and before function exit (post). */ > +static void > +instr_func (gimple_seq *pre, gimple_seq *post) > +{ > + /* In this case we need no instrumentation for the function */ > + if (func_calls == 0 && func_mops == 0) > + return; > + > + if (func_ignore != tsan_ignore_rec) > + { > + build_stack_assign (pre); > + build_stack_op (pre, false); > + build_stack_op (post, true); > + } > + > + if (func_ignore == tsan_ignore_rec && func_calls != 0) > + { > + build_rec_ignore_op (pre, PLUS_EXPR); > + build_rec_ignore_op (post, MINUS_EXPR); > + } > +} > + > +/* Sets location for all gimples in the seq. */ > +static void > +set_location (gimple_seq seq, location_t loc) > +{ > + gimple_seq_node n; > + > + for (n = gimple_seq_first (seq); n != NULL; n = n->next) > + gimple_set_location (n->stmt, loc); > +} > + > +/* Check as to whether expr refers to a store to vptr. */ > +static tree > +is_dtor_vptr_store (gimple stmt, tree expr, int is_store) > +{ > + if (is_store == 1 > + && TREE_CODE (expr) == COMPONENT_REF > + && gimple_assign_single_p (stmt) > + && strcmp (decl_name (cfun->decl), "__base_dtor ") == 0) > + { > + tree comp = expr->exp.operands [0]; > + while (TREE_CODE (comp) == COMPONENT_REF) > + comp = comp->exp.operands [0]; > + if (TREE_CODE (comp) == INDIRECT_REF || TREE_CODE (comp) == MEM_REF) > + { > + comp = comp->exp.operands [0]; > + if (TREE_CODE (comp) == SSA_NAME) > + comp = SSA_NAME_VAR (comp); > + if (strcmp (decl_name (comp), "this") == 0) > + { > + tree field = expr->exp.operands [1]; > + if (TREE_CODE (field) == FIELD_DECL > + && strncmp (decl_name (field), > + "_vptr.", sizeof ("_vptr.") - 1) == 0) > + return gimple_assign_rhs1 (stmt); > + } > + } > + } > + return 0; > +} > + > +/* Checks as to whether expr refers to a read from vtlb. > + Vtlbs are immutable, so don't bother to instrument them. */ > +static int > +is_vtbl_read (tree expr, int is_store) > +{ > + /* We may not instrument reads from vtbl, because the data is constant. > + vtbl read is of the form: > + gimple_assign <component_ref, D.2133, x->_vptr.X, NULL> > + gimple_assign <indirect_ref, D.2134, *D.2133, NULL> > + or: > + gimple_assign <component_ref, D.2133, x->_vptr.X, NULL> > + gimple_assign <pointer_plus_expr, D.2135, D.2133, 8> > + gimple_assign <indirect_ref, D.2136, *D.2135, NULL> */ > + > + if (is_store == 0 > + && TREE_CODE (expr) == INDIRECT_REF) > + { > + tree ref_target = expr->exp.operands [0]; > + if (TREE_CODE (ref_target) == SSA_NAME) > + { > + gimple ref_stmt = ref_target->ssa_name.def_stmt; > + if (gimple_code (ref_stmt) == GIMPLE_ASSIGN) > + { > + if (gimple_expr_code (ref_stmt) == POINTER_PLUS_EXPR) > + { > + tree tmp = ref_stmt->gsmem.op [1]; > + if (TREE_CODE (tmp) == SSA_NAME > + && gimple_code (tmp->ssa_name.def_stmt) == GIMPLE_ASSIGN) > + ref_stmt = tmp->ssa_name.def_stmt; > + } > + if (gimple_expr_code (ref_stmt) == COMPONENT_REF > + && gimple_assign_single_p (ref_stmt)) > + { > + tree comp_expr = ref_stmt->gsmem.op [1]; > + tree field_expr = comp_expr->exp.operands [1]; > + if (TREE_CODE (field_expr) == FIELD_DECL > + && strncmp (decl_name (field_expr), > + "_vptr.", sizeof ("_vptr.") - 1) == 0) > + return 1; > + } > + } > + } > + } > + > + return 0; > +} > + > +/* Checks as to whether expr refers to constant var/field/param. > + Don't bother to instrument them. */ > +static int > +is_load_of_const (tree expr, int is_store) > +{ > + if (is_store == 0) > + { > + if (TREE_CODE (expr) == COMPONENT_REF) > + expr = expr->exp.operands [1]; > + if (TREE_CODE (expr) == VAR_DECL > + || TREE_CODE (expr) == PARM_DECL > + || TREE_CODE (expr) == FIELD_DECL) > + { > + if (TREE_READONLY (expr)) > + return 1; > + } > + } > + return 0; > +} > + > +static void > +handle_expr (gimple stmt, gimple_stmt_iterator gsi, > + tree expr, int is_store, VEC (mop_desc_t, heap) **mop_list) > +{ > + enum tree_code tcode; > + struct mop_desc_t mop; > + unsigned fld_off; > + unsigned fld_size; > + > + /* map SSA name to real name */ > + if (TREE_CODE (expr) == SSA_NAME) > + expr = SSA_NAME_VAR (expr); > + > + tcode = TREE_CODE (expr); > + > + /* Below are things we do NOT want to instrument. */ > + if (func_ignore & (tsan_ignore_mop | tsan_ignore_rec)) > + { > + return; > + } > + else if (TREE_CODE_CLASS (tcode) == tcc_constant) > + { > + /* various constant literals */ > + return; > + } > + else if (TREE_CODE_CLASS (tcode) == tcc_declaration > + && DECL_ARTIFICIAL (expr)) > + { > + /* compiler-emitted artificial variables */ > + return; > + } > + if (tcode == RESULT_DECL) > + { > + /* store to function result */ > + return; > + } > + else if (tcode == VAR_DECL > + && TREE_ADDRESSABLE (expr) == 0 > + && TREE_STATIC (expr) == 0) > + { > + /* the var does not live in memory -> no possibility of races */ > + return; > + } > + else if (TREE_CODE (TREE_TYPE (expr)) == RECORD_TYPE) > + { > + /* TODO (dvyukov): implement me */ > + return; > + } > + else if (tcode == CONSTRUCTOR) > + { > + /* TODO (dvyukov): implement me */ > + return; > + } > + else if (tcode == PARM_DECL) > + { > + /* TODO (dvyukov): implement me */ > + return; > + } > + else if (is_load_of_const (expr, is_store)) > + { > + /* load of a const variable/parameter/field */ > + return; > + } > + else if (is_vtbl_read (expr, is_store)) > + { > + /* vtbl read */ > + return; > + } > + else if (tcode == COMPONENT_REF) > + { > + tree field = expr->exp.operands [1]; > + if (TREE_CODE (field) == FIELD_DECL) > + { > + fld_off = field->field_decl.bit_offset->int_cst.int_cst.low; > + fld_size = field->decl_common.size->int_cst.int_cst.low; > + if (((fld_off % BITS_PER_UNIT) != 0) > + || ((fld_size % BITS_PER_UNIT) != 0)) > + { > + /* As of now it crashes compilation. > + TODO (dvyukov): handle bit-fields as if touching the whole field */ > + return; > + } > + } > + } > + > + /* TODO (dvyukov): handle other cases > + (FIELD_DECL, MEM_REF, ARRAY_RANGE_REF, TARGET_MEM_REF, ADDR_EXPR) */ > + if (tcode != ARRAY_REF > + && tcode != VAR_DECL > + && tcode != COMPONENT_REF > + && tcode != INDIRECT_REF > + && tcode != MEM_REF) > + return; > + > + mop.is_call = 0; > + mop.gsi = gsi; > + mop.expr = expr; > + mop.dtor_vptr_expr = is_dtor_vptr_store (stmt, expr, is_store); > + mop.is_store = is_store; > + VEC_safe_push (mop_desc_t, heap, *mop_list, &mop); > +} > + > +static void > +handle_gimple (gimple_stmt_iterator gsi, VEC (mop_desc_t, heap) **mop_list) > +{ > + unsigned i; > + struct mop_desc_t mop; > + gimple stmt; > + enum gimple_code gcode; > + location_t loc; > + tree rhs; > + tree lhs; > + > + stmt = gsi_stmt (gsi); > + gcode = gimple_code (stmt); > + if (gcode >= LAST_AND_UNUSED_GIMPLE_CODE) > + return; > + > + loc = gimple_location (stmt); > + > + switch (gcode) > + { > + /* TODO (dvyukov): handle GIMPLE_COND (can it access memmory?) */ > + case GIMPLE_CALL: > + { > + func_calls += 1; > + /* Handle call arguments as loads */ > + for (i = 0; i < gimple_call_num_args (stmt); i++) > + { > + rhs = gimple_call_arg (stmt, i); > + handle_expr (stmt, gsi, rhs, 0, mop_list); > + } > + > + memset (&mop, 0, sizeof (mop)); > + mop.is_call = 1; > + VEC_safe_push (mop_desc_t, heap, *mop_list, &mop); > + > + /* Handle assignment lhs as store */ > + lhs = gimple_call_lhs (stmt); > + if (lhs != 0) > + handle_expr (stmt, gsi, lhs, 1, mop_list); > + > + break; > + } > + > + case GIMPLE_ASSIGN: > + { > + /* Handle assignment lhs as store */ > + lhs = gimple_assign_lhs (stmt); > + handle_expr (stmt, gsi, lhs, 1, mop_list); > + > + /* Handle operands as loads */ > + for (i = 1; i < gimple_num_ops (stmt); i++) > + { > + rhs = gimple_op (stmt, i); > + handle_expr (stmt, gsi, rhs, 0, mop_list); > + } > + break; > + } > + > + case GIMPLE_BIND: > + { > + gcc_assert (!"there should be no GIMPLE_BIND on this level"); > + break; > + } > + > + default: > + break; > + } > +} > + > +/* Instruments single basic block. */ > +static void > +instrument_bblock (struct bb_data_t *bbd, basic_block bb) > +{ > + int ix; > + int is_sblock; > + gimple_stmt_iterator gsi; > + struct mop_desc_t *mop; > + gimple stmt; > + location_t loc; > + expanded_location eloc; > + gimple_seq instr_seq; > + > + /* Iterate over all gimples and collect interesting mops into mop_list. */ > + VEC_free (mop_desc_t, heap, mop_list); > + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) > + { > + handle_gimple (gsi, &mop_list); > + } > + > + mop = 0; > + for (ix = 0; VEC_iterate (mop_desc_t, mop_list, ix, mop); ix += 1) > + { > + if (mop->is_call != 0) > + { > + /* After a function call we must start a brand new sblock, > + because the function can contain synchronization. */ > + bbd->has_sb = 0; > + continue; > + } > + > + func_mops += 1; > + stmt = gsi_stmt (mop->gsi); > + loc = gimple_location (stmt); > + eloc = expand_location (loc); > + > + /* Check as to whether we may not set sblock flag > + for the access */ > + is_sblock = (bbd->has_sb == 0 > + || !(eloc.file != 0 > + && bbd->sb_file != 0 > + && strcmp (eloc.file, bbd->sb_file) == 0 > + && eloc.line >= bbd->sb_line_min > + && eloc.line <= bbd->sb_line_max)); > + > + if (func_ignore == tsan_ignore_hist) > + is_sblock = 0; > + > + if (is_sblock) > + { > + /* Start new sblock with new source info. */ > + bbd->has_sb = 1; > + bbd->sb_file = eloc.file; > + bbd->sb_line_min = eloc.line; > + bbd->sb_line_max = eloc.line + SBLOCK_SIZE; > + } > + > + instr_seq = 0; > + if (mop->dtor_vptr_expr == 0) > + instr_mop (mop->expr, mop->is_store, is_sblock, &instr_seq); > + else > + instr_vptr_store (mop->expr, mop->dtor_vptr_expr, is_sblock, &instr_seq); > + gcc_assert (instr_seq != 0); > + set_location (instr_seq, loc); > + /* Instrumentation for assignment of a function result > + must be inserted after the call. Instrumentation for > + reads of function arguments must be inserted before the call. > + That's because the call can contain synchronization. */ > + if (is_gimple_call (stmt) && mop->is_store == 1) > + gsi_insert_seq_after (&mop->gsi, instr_seq, GSI_NEW_STMT); > + else > + gsi_insert_seq_before (&mop->gsi, instr_seq, GSI_SAME_STMT); > + } > +} > + > +/* Instruments all interesting memory accesses in the function */ > +static void > +instrument_mops (void) > +{ > + int sb_line_min; > + int sb_line_max; > + int bb_cnt; > + int eidx; > + basic_block bb; > + basic_block entry_bb; > + basic_block cur_bb; > + basic_block any_bb; > + struct bb_data_t *pred; > + struct bb_data_t *succ; > + struct bb_data_t *bb_data; > + struct bb_data_t *bbd; > + edge entry_edge; > + edge e; > + > + /* The function does breadth-first traversal of CFG. > + BB is visited preferably if all its predecessors are visited. > + Such order is required to properly mark super-blocks. > + The idea behind super-blocks is as follows. > + If several memory accesses happen within SBLOCK_SIZE source code lines > + from each other, then we only mark the first access as SBLOCK. > + This allows runtime library to memorize stack trace > + only for the first access and do not memorize for others. > + This significantly reduces memory consumption in exchange for slightly > + imprecise stack traces for previous accesses. */ > + > + /* First, mark all blocks as not visited, and entry block as candidate. */ > + bb_cnt = cfun->cfg->x_n_basic_blocks; > + bb_data = (struct bb_data_t*) xcalloc (bb_cnt, sizeof (struct bb_data_t)); > + entry_bb = ENTRY_BLOCK_PTR; > + entry_edge = single_succ_edge (entry_bb); > + entry_bb = entry_edge->dest; > + bb = 0; > + FOR_EACH_BB (bb) > + { > + bb_data [bb->index].state = (bb == entry_bb) ? bb_candidate : bb_not_visited; > + } > + > + /* Until all blocks are visited. */ > + for (; ; ) > + { > + cur_bb = 0; > + any_bb = 0; > + /* Look for a candidate with all visited predecessors. */ > + FOR_EACH_BB (bb) > + { > + bbd = &bb_data [bb->index]; > + if (bbd->state == bb_candidate) > + { > + cur_bb = bb; > + any_bb = bb; > + e = 0; > + for (eidx = 0; VEC_iterate (edge, bb->preds, eidx, e); eidx++) > + { > + pred = &bb_data [e->src->index]; > + if (pred->state != bb_visited) > + { > + cur_bb = 0; > + break; > + } > + } > + } > + if (cur_bb != 0) > + break; > + } > + /* All blocks are visited. */ > + if (any_bb == 0) > + break; > + /* If no blocks with all visited predecessors, choose any candidate. > + Must be a loop. */ > + cur_bb = cur_bb ? cur_bb : any_bb; > + bbd = &bb_data [cur_bb->index]; > + gcc_assert (bbd->state == bb_candidate); > + bbd->state = bb_visited; > + > + /* Iterate over all predecessors and merge their sblock info. */ > + e = 0; > + for (eidx = 0; VEC_iterate (edge, cur_bb->preds, eidx, e); eidx++) > + { > + pred = &bb_data [e->src->index]; > + if ((pred->state != bb_visited) > + || (pred->has_sb == 0) > + || (pred == bbd)) > + { > + /* If there is a not visited predecessor, > + or a predecessor with no active sblock info, > + or a self-loop, then we will have to start > + a brand new sblock on next memory access. */ > + bbd->has_sb = 0; > + break; > + } > + else if (bbd->has_sb == 0) > + { > + /* If it's a first predecessor, just copy the info. */ > + bbd->has_sb = 1; > + bbd->sb_file = pred->sb_file; > + bbd->sb_line_min = pred->sb_line_min; > + bbd->sb_line_max = pred->sb_line_max; > + } > + else > + { > + /* Otherwise, find the interception > + between two sblock descriptors. */ > + bbd->has_sb = 0; > + if (bbd->sb_file != 0 && pred->sb_file != 0 > + && strcmp (bbd->sb_file, pred->sb_file) == 0) > + { > + sb_line_min = MAX (bbd->sb_line_min, pred->sb_line_min); > + sb_line_max = MIN (bbd->sb_line_max, pred->sb_line_max); > + if (sb_line_min <= sb_line_max) > + { > + bbd->has_sb = 1; > + bbd->sb_line_min = sb_line_min; > + bbd->sb_line_max = sb_line_max; > + } > + } > + /* No interception, have to start new sblock. */ > + if (bbd->has_sb == 0) > + break; > + } > + } > + > + /* Finally, instrument the block. */ > + instrument_bblock (bbd, cur_bb); > + > + /* Mark all successors as candidates. */ > + for (eidx = 0; VEC_iterate (edge, cur_bb->succs, eidx, e); eidx++) > + { > + succ = &bb_data [e->dest->index]; > + if (succ->state == bb_not_visited) > + succ->state = bb_candidate; > + } > + } > +} > + > +/* Instruments function entry and exit, if necessary. */ > +static void > +instrument_function (void) > +{ > + location_t loc; > + gimple_seq pre_func_seq; > + gimple_seq post_func_seq; > + basic_block entry_bb; > + basic_block first_bb; > + basic_block bb; > + edge entry_edge; > + gimple_stmt_iterator first_gsi; > + gimple_stmt_iterator gsi; > + gimple_stmt_iterator gsi2; > + gimple first_stmt; > + gimple stmt; > + > + pre_func_seq = 0; > + post_func_seq = 0; > + instr_func (&pre_func_seq, &post_func_seq); > + > + if (pre_func_seq != 0) > + { > + /* Insert new BB before the first BB. */ > + entry_bb = ENTRY_BLOCK_PTR; > + entry_edge = single_succ_edge (entry_bb); > + first_bb = entry_edge->dest; > + first_gsi = gsi_start_bb (first_bb); > + if (!gsi_end_p (first_gsi)) > + { > + first_stmt = gsi_stmt (first_gsi); > + loc = gimple_location (first_stmt); > + set_location (pre_func_seq, loc); > + } > + entry_bb = split_edge (entry_edge); > + gsi = gsi_start_bb (entry_bb); > + gsi_insert_seq_after (&gsi, pre_func_seq, GSI_NEW_STMT); > + } > + > + if (post_func_seq != 0) > + { > + /* Find all function exits. */ > + FOR_EACH_BB (bb) > + { > + gsi2 = gsi_start_bb (bb); > + for (; ; ) > + { > + gsi = gsi2; > + if (gsi_end_p (gsi)) > + break; > + gsi_next (&gsi2); > + > + stmt = gsi_stmt (gsi); > + loc = gimple_location (stmt); > + > + if (gimple_code (stmt) == GIMPLE_RETURN) > + { > + set_location (post_func_seq, loc); > + gsi_insert_seq_before (&gsi, post_func_seq, GSI_SAME_STMT); > + } > + } > + } > + } > +} > + > +static unsigned > +tsan_pass (void) > +{ > + if (errorcount != 0 || sorrycount != 0) > + return 0; > + > + func_ignore = tsan_ignore (); > + if (func_ignore == tsan_ignore_func) > + return 0; > + > + func_calls = 0; > + func_mops = 0; > + > + instrument_mops (); > + instrument_function (); > + > + return 0; > +} > + > +static bool > +tsan_gate (void) > +{ > + return flag_tsan != 0; > +} > + > +struct gimple_opt_pass pass_tsan = {{ > + GIMPLE_PASS, > + "tsan", /* name */ > + tsan_gate, /* gate */ > + tsan_pass, /* execute */ > + NULL, /* sub */ > + NULL, /* next */ > + 0, /* static_pass_number */ > + TV_NONE, /* tv_id */ > + PROP_trees | PROP_cfg, /* properties_required */ > + 0, /* properties_provided */ > + 0, /* properties_destroyed */ > + 0, /* todo_flags_start */ > + TODO_dump_cgraph | TODO_dump_func | TODO_verify_all > + | TODO_update_ssa | TODO_update_address_taken /* todo_flags_finish */ > +}}; > + > > Property changes on: gcc/tree-tsan.c > ___________________________________________________________________ > Added: svn:eol-style > + LF > > Index: gcc/tree-pass.h > =================================================================== > --- gcc/tree-pass.h (revision 180522) > +++ gcc/tree-pass.h (working copy) > @@ -352,6 +352,7 @@ > > extern struct gimple_opt_pass pass_mudflap_1; > extern struct gimple_opt_pass pass_mudflap_2; > +extern struct gimple_opt_pass pass_tsan; > extern struct gimple_opt_pass pass_lower_cf; > extern struct gimple_opt_pass pass_refactor_eh; > extern struct gimple_opt_pass pass_lower_eh; > Index: gcc/testsuite/gcc.dg/tsan-ignore.ignore > =================================================================== > --- gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) > +++ gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) > @@ -0,0 +1,7 @@ > +#comment > +fun:foo > +fun:*bar > +fun:baz* > +fun:*bla* > +fun:x*x > +src:tsan-ignore.h > Index: gcc/testsuite/gcc.dg/tsan.h > =================================================================== > --- gcc/testsuite/gcc.dg/tsan.h (revision 0) > +++ gcc/testsuite/gcc.dg/tsan.h (revision 0) > @@ -0,0 +1,87 @@ > +/* Helper declarations and functions for ThreadSanitizer instrumentation (-ftsan) testing */ > + > +int printf (char *str, ...); > +void exit (int); > + > +/* Variables referenced by the instrumentation */ > +__thread void **__tsan_shadow_stack; > +__thread int __tsan_thread_ignore; > + > +/* Local helper vars */ > +__thread void *shadow_stack[1024]; > +__thread int mop_expect; > +__thread int mop_depth; > +__thread void* mop_addr; > +__thread unsigned long long mop_pc; > +__thread unsigned mop_flags; > + > +/* Setups shadow stack var (not instrumented) */ > +void __attribute__ ((constructor)) > +__tsan_init (void) > +{ > + __tsan_shadow_stack = shadow_stack; > +} > + > +/* Declare that we expect an instrumented memory access (not instrumented). > + depth - stack depth of the mop (0 - main, 1 - func called from main and so on). > + addr - memory access address. > + is_store - store/load. > + is_sblock - superblock flag of the access. > + size - memory access size in bytes. */ > +void > +__tsan_expect_mop (int depth, void *addr, int is_store, int is_sblock, int size) > +{ > + if (mop_expect) > + { > + printf ("missed mop: addr=%p pc=%d\n", mop_addr, mop_pc); > + exit (1); > + } > + > + mop_expect = 1; > + mop_depth = depth; > + mop_addr = addr; > + mop_pc = (unsigned long long)__builtin_return_address(0); > + mop_flags = !!is_sblock | (!!is_store << 1) | ((size - 1) << 2); > +} > + > +/* Memory access function (referenced by instrumentation, not instrumented). */ > +void > +__tsan_handle_mop (void *addr, unsigned flags) > +{ > + unsigned long long pc; > + int depth; > + > + printf ("mop: addr=%p flags=%x called from %p/%p\n", addr, flags, __builtin_return_address(1), __tsan_shadow_stack[-2]); > + if (mop_expect == 0) > + return; > + > + /* Verify parameters with what we expect. */ > + > + if (addr != mop_addr) > + { > + printf ("incorrect mop addr: %p/%p\n", addr, mop_addr); > + exit (1); > + } > + > + pc = (unsigned long long)__builtin_return_address(0); > + if (pc < mop_pc - 100 || pc > mop_pc + 100) > + { > + printf ("incorrect mop pc: %p/%p\n", (void*)pc, (void*)mop_pc); > + exit (1); > + } > + > + depth = __tsan_shadow_stack - shadow_stack - 1; > + if (depth != mop_depth) > + { > + printf ("incorrect mop depth: %d/%d\n", depth, mop_depth); > + exit (1); > + } > + > + if (flags != mop_flags) > + { > + printf ("incorrect mop flags: %x/%x\n", flags, mop_flags); > + exit (1); > + } > + > + mop_expect = 0; > +} > > Property changes on: gcc/testsuite/gcc.dg/tsan.h > ___________________________________________________________________ > Added: svn:eol-style > + LF > > Index: gcc/testsuite/gcc.dg/tsan-ignore.c > =================================================================== > --- gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) > +++ gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) > @@ -0,0 +1,49 @@ > +/* { dg-do run } */ > +/* { dg-options "-ftsan -ftsan-ignore=tsan-ignore.ignore" } */ > +#include "tsan.h" > +#include "tsan-ignore.h" > + > +/* Check ignore file handling. */ > + > +int > +foo (int *p) > +{ > + p [0] = 1; > +} > + > +int bar (int *p) > +{ > + p [0] = 1; > +} > + > +int baz (int *p) > +{ > + p [0] = 1; > +} > + > +int bla (int *p) > +{ > + p [0] = 1; > +} > + > +int xxx (int *p) > +{ > + p [0] = 1; > +} > + > +int > +main (void) > +{ > + int p, x; > + > + __tsan_expect_mop(0, &p, 1, 1, sizeof(p)); > + /* All these functions must be ignored. */ > + foo (&x); > + bar (&x); > + baz (&x); > + bla (&x); > + xxx (&x); > + in_tsan_ignore_header (&x); > + p = 0; > + return 0; > +} > > Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.c > ___________________________________________________________________ > Added: svn:eol-style > + LF > > Index: gcc/testsuite/gcc.dg/tsan-ignore.h > =================================================================== > --- gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) > +++ gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) > @@ -0,0 +1,5 @@ > +int > +in_tsan_ignore_header (int *p) > +{ > + p [0] = 1; > +} > > Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.h > ___________________________________________________________________ > Added: svn:eol-style > + LF > > Index: gcc/testsuite/gcc.dg/tsan-stack.c > =================================================================== > --- gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) > +++ gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) > @@ -0,0 +1,23 @@ > +/* { dg-do run } */ > +/* { dg-options "-ftsan" } */ > +#include "tsan.h" > + > +/* Check shadow stack maintance. */ > + > +int > +foobar (int *p) > +{ > + __tsan_expect_mop(1, p, 1, 1, sizeof(*p)); > + p[0] = 1; > +} > + > +int > +main (void) > +{ > + int p; > + > + __tsan_expect_mop(0, &p, 1, 1, sizeof(p)); > + p = 0; > + foobar (&p); > + return 0; > +} > > Property changes on: gcc/testsuite/gcc.dg/tsan-stack.c > ___________________________________________________________________ > Added: svn:eol-style > + LF > > Index: gcc/testsuite/gcc.dg/tsan-mop.c > =================================================================== > --- gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) > +++ gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) > @@ -0,0 +1,29 @@ > +/* { dg-do run } */ > +/* { dg-options "-ftsan" } */ > +#include "tsan.h" > + > +/* Sanity check for memory accesses instrumentation. */ > + > +int > +foobar (int *p) > +{ > + __tsan_expect_mop(1, p, 1, 1, sizeof(*p)); > + p[0] = 1; > + > + __tsan_expect_mop(1, p, 1, 1, sizeof(*p)); > + *p = 2; > + > + __tsan_expect_mop(1, (char*)p+3, 1, 1, 1); > + *((char*)p+3) = 3; > +} > + > +int > +main (void) > +{ > + int p; > + > + __tsan_expect_mop(0, &p, 1, 1, sizeof(p)); > + p = 0; > + foobar (&p); > + return 0; > +} > > Property changes on: gcc/testsuite/gcc.dg/tsan-mop.c > ___________________________________________________________________ > Added: svn:eol-style > + LF > > Index: gcc/common.opt > =================================================================== > --- gcc/common.opt (revision 180522) > +++ gcc/common.opt (working copy) > @@ -1547,6 +1547,14 @@ > Common RejectNegative Report Var(flag_mudflap_ignore_reads) > Ignore read operations when inserting mudflap instrumentation > > +ftsan > +Common RejectNegative Report Var(flag_tsan) > +Add ThreadSanitizer instrumentation > + > +ftsan-ignore= > +Common RejectNegative Joined Var(flag_tsan_ignore) > +-ftsan-ignore=filename ThreadSanitizer ignore file > + > fdce > Common Var(flag_dce) Init(1) Optimization > Use the RTL dead code elimination pass > Index: gcc/Makefile.in > =================================================================== > --- gcc/Makefile.in (revision 180522) > +++ gcc/Makefile.in (working copy) > @@ -1494,6 +1494,7 @@ > tree-streamer-out.o \ > tree-tailcall.o \ > tree-threadsafe-analyze.o \ > + tree-tsan.o \ > tree-vect-generic.o \ > tree-vect-patterns.o \ > tree-vect-data-refs.o \ > @@ -2814,6 +2815,12 @@ > $(C_TREE_H) $(C_COMMON_H) $(GIMPLE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) \ > output.h langhooks.h tree-mudflap.h $(TM_H) coretypes.h \ > $(GGC_H) gt-tree-mudflap.h $(TREE_PASS_H) $(DIAGNOSTIC_CORE_H) > +tree-tsan.o : $(CONFIG_H) $(SYSTEM_H) $(TREE_H) $(TREE_INLINE_H) \ > + $(GIMPLE_H) $(DIAGNOSTIC_H) langhooks.h \ > + $(TM_H) coretypes.h $(TREE_DUMP_H) $(TREE_PASS_H) $(CGRAPH_H) $(GGC_H) \ > + $(BASIC_BLOCK_H) $(FLAGS_H) $(FUNCTION_H) \ > + $(TM_P_H) $(TREE_FLOW_H) $(DIAGNOSTIC_CORE_H) $(GIMPLE_H) tree-iterator.h \ > + intl.h cfghooks.h output.h options.h c-family/c-common.h > tree-pretty-print.o : tree-pretty-print.c $(CONFIG_H) $(SYSTEM_H) \ > $(TREE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) $(TREE_FLOW_H) \ > $(TM_H) coretypes.h tree-iterator.h $(SCEV_H) langhooks.h \ > Index: gcc/passes.c > =================================================================== > --- gcc/passes.c (revision 180522) > +++ gcc/passes.c (working copy) > @@ -1420,6 +1420,7 @@ > NEXT_PASS (pass_lower_resx); > NEXT_PASS (pass_nrv); > NEXT_PASS (pass_mudflap_2); > + NEXT_PASS (pass_tsan); > NEXT_PASS (pass_cleanup_cfg_post_optimizing); > NEXT_PASS (pass_warn_function_noreturn); > > > -- > This patch is available for review at http://codereview.appspot.com/5303083 > > -- > You received this message because you are subscribed to the Google Groups "C-compiler-team" group. > To post to this group, send email to c-compiler-team@google.com. > To unsubscribe from this group, send email to c-compiler-team+unsubscribe@google.com. > For more options, visit this group at http://groups.google.com/a/google.com/group/c-compiler-team/?hl=en. > >
Sign in to reply to this message.
[ Removing closed list from CCs, please trim CC in future replies. Thanks. ] On Fri, Oct 28, 2011 at 08:53, Xinliang David Li <davidxl@google.com> wrote: > Dmitriy, I will review it next week. > > Do you have some runtime overhead data ? > > thanks, > > David > > On Fri, Oct 28, 2011 at 6:25 AM, Dmitriy Vyukov <dvyukov@google.com> wrote: >> The patch is for google/main branch. >> ThreadSanitizer is a data race detector for C/C++ programs. >> http://code.google.com/p/data-race-test/wiki/ThreadSanitizer >> >> The tool consists of two parts: >> instrumentation module (this file) and a run-time library. >> The instrumentation module mainintains shadow call stacks >> and intercepts interesting memory accesses. >> The instrumentation is enabled with -ftsan flag. >> >> Instrumentation for shadow stack maintainance is as follows: >> void somefunc () >> { >> __tsan_shadow_stack [-1] = __builtin_return_address (0); >> __tsan_shadow_stack++; >> // function body >> __tsan_shadow_stack--; >> } >> >> Interception for memory access interception is as follows: >> *addr = 1; >> __tsan_handle_mop (addr, flags); >> where flags are (is_sblock | (is_store << 1) | ((sizeof (*addr) - 1) << 2). >> is_sblock is used merely for optimization purposes and can always >> be set to 1, see comments in instrument_mops function. >> >> Ignore files can be used to selectively non instrument some functions. >> Ignore file is specified with -ftsan-ignore=filename flag. >> There are 3 types of ignores: (1) do not instrument memory accesses >> in the function, (2) do not create sblocks in the function >> and (3) recursively ignore memory accesses in the function. >> That last ignore type requires additional instrumentation of the form: >> void somefunc () >> { >> __tsan_thread_ignore++; >> // function body >> __tsan_thread_ignore--; >> } >> >> The run-time library provides __tsan_handle_mop function, >> definitions of __tsan_shadow_stack and __tsan_thread_ignore variables, >> and intercepts synchronization related functions. >> >> 2011-10-28 Dmitriy Vyukov <dvyukov@google.com> >> >> * gcc/doc/invoke.texi: >> * gcc/tree-tsan.c (enum tsan_ignore_e): >> (enum bb_state_e): >> (struct bb_data_t): >> (struct mop_desc_t): >> (struct tsan_ignore_desc_t): >> (lookup_name): >> (shadow_stack_def): >> (thread_ignore_def): >> (rtl_mop_def): >> (ignore_append): >> (ignore_match): >> (ignore_load): >> (tsan_ignore): >> (decl_name): >> (build_stack_op): >> (build_rec_ignore_op): >> (build_stack_assign): >> (instr_mop): >> (instr_vptr_store): >> (instr_func): >> (set_location): >> (is_dtor_vptr_store): >> (is_vtbl_read): >> (is_load_of_const): >> (handle_expr): >> (handle_gimple): >> (instrument_bblock): >> (instrument_mops): >> (instrument_function): >> (tsan_pass): >> (tsan_gate): >> * gcc/tree-pass.h: >> * gcc/testsuite/gcc.dg/tsan-ignore.ignore: >> * gcc/testsuite/gcc.dg/tsan.h (__tsan_init): >> (__tsan_expect_mop): >> (__tsan_handle_mop): >> * gcc/testsuite/gcc.dg/tsan-ignore.c (foo): >> (int bar): >> (int baz): >> (int bla): >> (int xxx): >> (main): >> * gcc/testsuite/gcc.dg/tsan-ignore.h (in_tsan_ignore_header): >> * gcc/testsuite/gcc.dg/tsan-stack.c (foobar): >> * gcc/testsuite/gcc.dg/tsan-mop.c: >> * gcc/common.opt: >> * gcc/Makefile.in: >> * gcc/passes.c: >> >> Index: gcc/doc/invoke.texi >> =================================================================== >> --- gcc/doc/invoke.texi (revision 180522) >> +++ gcc/doc/invoke.texi (working copy) >> @@ -308,6 +308,7 @@ >> -fdump-tree-ssa@r{[}-@var{n}@r{]} -fdump-tree-pre@r{[}-@var{n}@r{]} @gol >> -fdump-tree-ccp@r{[}-@var{n}@r{]} -fdump-tree-dce@r{[}-@var{n}@r{]} @gol >> -fdump-tree-gimple@r{[}-raw@r{]} -fdump-tree-mudflap@r{[}-@var{n}@r{]} @gol >> +-fdump-tree-tsan@r{[}-@var{n}@r{]} @gol >> -fdump-tree-dom@r{[}-@var{n}@r{]} @gol >> -fdump-tree-dse@r{[}-@var{n}@r{]} @gol >> -fdump-tree-phiprop@r{[}-@var{n}@r{]} @gol >> @@ -381,8 +382,8 @@ >> -floop-parallelize-all -flto -flto-compression-level @gol >> -flto-partition=@var{alg} -flto-report -fmerge-all-constants @gol >> -fmerge-constants -fmodulo-sched -fmodulo-sched-allow-regmoves @gol >> --fmove-loop-invariants fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol >> --fno-default-inline @gol >> +-fmove-loop-invariants -fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol >> +-ftsan -ftsan-ignore -fno-default-inline @gol >> -fno-defer-pop -fno-function-cse -fno-guess-branch-probability @gol >> -fno-inline -fno-math-errno -fno-peephole -fno-peephole2 @gol >> -fno-sched-interblock -fno-sched-spec -fno-signed-zeros @gol >> @@ -5896,6 +5897,11 @@ >> Dump each function after adding mudflap instrumentation. The file name is >> made by appending @file{.mudflap} to the source file name. >> >> +@item tsan >> +@opindex fdump-tree-tsan >> +Dump each function after adding ThreadSanitizer instrumentation. The file name is >> +made by appending @file{.tsan} to the source file name. >> + >> @item sra >> @opindex fdump-tree-sra >> Dump each function after performing scalar replacement of aggregates. The >> @@ -6674,6 +6680,12 @@ >> some protection against outright memory corrupting writes, but allows >> erroneously read data to propagate within a program. >> >> +@item -ftsan -ftsan-ignore >> +@opindex ftsan >> +@opindex ftsan-ignore >> +Add ThreadSanitizer instrumentation. Use @option{-ftsan-ignore} to specify >> +an ignore file. Refer to http://go/tsan for details. >> + >> @item -fthread-jumps >> @opindex fthread-jumps >> Perform optimizations where we check to see if a jump branches to a >> Index: gcc/tree-tsan.c >> =================================================================== >> --- gcc/tree-tsan.c (revision 0) >> +++ gcc/tree-tsan.c (revision 0) >> @@ -0,0 +1,1204 @@ >> +/* ThreadSanitizer instrumentation pass. >> + http://code.google.com/p/data-race-test >> + Copyright (C) 2011 >> + Free Software Foundation, Inc. >> + Contributed by Dmitry Vyukov <dvyukov@google.com> >> + >> +This file is part of GCC. >> + >> +GCC is free software; you can redistribute it and/or modify >> +it under the terms of the GNU General Public License as published by >> +the Free Software Foundation; either version 3, or (at your option) >> +any later version. >> + >> +GCC is distributed in the hope that it will be useful, >> +but WITHOUT ANY WARRANTY; without even the implied warranty of >> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> +GNU General Public License for more details. >> + >> +You should have received a copy of the GNU General Public License >> +along with GCC; see the file COPYING3. If not see >> +<http://www.gnu.org/licenses/>. */ >> + >> +#include "config.h" >> +#include "system.h" >> +#include "coretypes.h" >> +#include "tree.h" >> +#include "intl.h" >> +#include "tm.h" >> +#include "basic-block.h" >> +#include "gimple.h" >> +#include "function.h" >> +#include "tree-flow.h" >> +#include "tree-pass.h" >> +#include "cfghooks.h" >> +#include "langhooks.h" >> +#include "output.h" >> +#include "options.h" >> + >> +/* The file can be compiled either as compiler pass or plugin. */ >> +#ifdef GCC_PLG >> +# include "c-common.h" >> +#else >> +# include "c-family/c-common.h" >> +#endif >> + >> +#include "diagnostic.h" >> + >> +#include <stdlib.h> >> +#include <stdio.h> >> + >> +/* ThreadSanitizer is a data race detector for C/C++ programs. >> + http://code.google.com/p/data-race-test/wiki/ThreadSanitizer >> + >> + The tool consists of two parts: >> + instrumentation module (this file) and a run-time library. >> + The instrumentation module mainintains shadow call stacks >> + and intercepts interesting memory accesses. >> + The instrumentation is enabled with -ftsan flag. >> + >> + Instrumentation for shadow stack maintainance is as follows: >> + void somefunc () >> + { >> + __tsan_shadow_stack [-1] = __builtin_return_address (0); >> + __tsan_shadow_stack++; >> + // function body >> + __tsan_shadow_stack--; >> + } >> + >> + Interception for memory access interception is as follows: >> + *addr = 1; >> + __tsan_handle_mop (addr, flags); >> + where flags are (is_sblock | (is_store << 1) | ((sizeof (*addr) - 1) << 2). >> + is_sblock is used merely for optimization purposes and can always >> + be set to 1, see comments in instrument_mops function. >> + >> + Ignore files can be used to selectively non instrument some functions. >> + Ignore file is specified with -ftsan-ignore=filename flag. >> + There are 3 types of ignores: (1) do not instrument memory accesses >> + in the function, (2) do not create sblocks in the function >> + and (3) recursively ignore memory accesses in the function. >> + That last ignore type requires additional instrumentation of the form: >> + void somefunc () >> + { >> + __tsan_thread_ignore++; >> + // function body >> + __tsan_thread_ignore--; >> + } >> + >> + The run-time library provides __tsan_handle_mop function, >> + definitions of __tsan_shadow_stack and __tsan_thread_ignore variables, >> + and intercepts synchronization related functions. */ >> + >> +#define RTL_IGNORE "__tsan_thread_ignore" >> +#define RTL_STACK "__tsan_shadow_stack" >> +#define RTL_MOP "__tsan_handle_mop" >> +#define RTL_PERFIX "__tsan_" >> +#define MAX_MOP_BYTES 16 >> +#define SBLOCK_SIZE 5 >> + >> +enum tsan_ignore_e >> +{ >> + tsan_ignore_none = 1 << 0, /* Do not ignore. */ >> + tsan_ignore_func = 1 << 1, /* Completely ignore the whole func. */ >> + tsan_ignore_mop = 1 << 2, /* Do not instrument memory accesses. */ >> + tsan_ignore_rec = 1 << 3, /* Do not instrument memory accesses recursively. */ >> + tsan_ignore_hist = 1 << 4 /* Do not create superblocks. */ >> +}; >> + >> +/* Basic block state during CFG traversal. */ >> +enum bb_state_e >> +{ >> + bb_not_visited, >> + bb_candidate, >> + bb_visited >> +}; >> + >> +/* Info associated with each basic block. >> + Used to determine super-blocks (see instrument_mops ()). */ >> +struct bb_data_t >> +{ >> + enum bb_state_e state; >> + int has_sb; >> + const char *sb_file; >> + int sb_line_min; >> + int sb_line_max; >> +}; >> + >> +/* Memory access descriptor. */ >> +struct mop_desc_t >> +{ >> + int is_call; >> + gimple_stmt_iterator gsi; >> + tree expr; >> + tree dtor_vptr_expr; >> + int is_store; >> +}; >> + >> +struct tsan_ignore_desc_t >> +{ >> + struct tsan_ignore_desc_t *next; >> + enum tsan_ignore_e type; >> + char *name; >> +}; >> + >> +/* Number of instrumented memory accesses in the current function. */ >> +static int func_mops; >> +/* Number of function calls in the current function. */ >> +static int func_calls; >> +/* Ignore status for the current function (see tsan_ignore_e). */ >> +static enum tsan_ignore_e func_ignore; >> + >> +static int ignore_init = 0; >> +static struct tsan_ignore_desc_t *ignore_head; >> + >> +typedef struct mop_desc_t mop_desc_t; >> +DEF_VEC_O (mop_desc_t); >> +DEF_VEC_ALLOC_O (mop_desc_t, heap); >> +static VEC (mop_desc_t, heap) *mop_list; >> + >> +/* The function is not available in some modules. */ >> +tree __attribute__((weak)) >> +lookup_name (tree t) >> +{ >> + (void)t; >> + return NULL_TREE; >> +} >> + >> +/* Builds the following decl >> + extern __thread void **__tsan_shadow_stack; */ >> +static tree >> +shadow_stack_def (void) >> +{ >> + static tree def; >> + >> + if (def != NULL) >> + return def; >> + >> + /* Check if a user has defined it for testing */ >> + def = lookup_name (get_identifier (RTL_STACK)); >> + if (def != NULL) >> + return def; >> + >> + def = build_decl (UNKNOWN_LOCATION, VAR_DECL, >> + get_identifier (RTL_STACK), >> + build_pointer_type (ptr_type_node)); >> + TREE_STATIC (def) = 1; >> + TREE_PUBLIC (def) = 1; >> + DECL_EXTERNAL (def) = 1; >> + DECL_TLS_MODEL (def) = decl_default_tls_model (def); >> + TREE_USED (def) = 1; >> + TREE_THIS_VOLATILE (def) = 1; >> + SET_DECL_ASSEMBLER_NAME (def, get_identifier (RTL_STACK)); >> + return def; >> +} >> + >> +/* Builds the following decl >> + extern __thread int __tsan_thread_ignore; */ >> +static tree >> +thread_ignore_def (void) >> +{ >> + static tree def; >> + >> + if (def != NULL) >> + return def; >> + >> + /* Check if a user has defined it for testing */ >> + def = lookup_name (get_identifier (RTL_IGNORE)); >> + if (def != NULL) >> + return def; >> + >> + def = build_decl (UNKNOWN_LOCATION, VAR_DECL, >> + get_identifier (RTL_IGNORE), >> + integer_type_node); >> + TREE_STATIC (def) = 1; >> + TREE_PUBLIC (def) = 1; >> + DECL_EXTERNAL (def) = 1; >> + DECL_TLS_MODEL (def) = decl_default_tls_model (def); >> + TREE_USED (def) = 1; >> + TREE_THIS_VOLATILE (def) = 1; >> + SET_DECL_ASSEMBLER_NAME (def, get_identifier (RTL_IGNORE)); >> + return def; >> +} >> + >> +/* Builds the following decl >> + void __tsan_handle_mop (void *addr, unsigned flags); */ >> +static tree >> +rtl_mop_def (void) >> +{ >> + tree fn_type; >> + >> + static tree def; >> + >> + if (def != NULL) >> + return def; >> + >> + /* Check if a user has defined it for testing */ >> + def = lookup_name (get_identifier (RTL_MOP)); >> + if (def != NULL) >> + return def; >> + >> + fn_type = build_function_type_list (void_type_node, ptr_type_node, >> + integer_type_node , NULL_TREE); >> + def = build_fn_decl (RTL_MOP, fn_type); >> + TREE_NOTHROW (def) = 1; >> + DECL_ATTRIBUTES (def) = tree_cons (get_identifier ("leaf"), >> + NULL, DECL_ATTRIBUTES (def)); >> + DECL_ASSEMBLER_NAME (def); >> + return def; >> +} >> + >> +/* Adds new ignore definition to the global list */ >> +static void >> +ignore_append (enum tsan_ignore_e type, char *name) >> +{ >> + struct tsan_ignore_desc_t *desc; >> + >> + desc = (struct tsan_ignore_desc_t*)xmalloc (sizeof (*desc)); >> + desc->type = type; >> + desc->name = xstrdup (name); >> + desc->next = ignore_head; >> + ignore_head = desc; >> +} >> + >> +/* Checks as to whether identifier 'str' matches template 'templ'. >> + Templates can only contain '*', e.g. 'std*string*insert'. >> + Templates implicitly start and end with '*' >> + since they are matched against mangled names. */ >> +static int >> +ignore_match (char *templ, const char *str) >> +{ >> + char *tpos; >> + const char *spos; >> + >> + while (templ && templ [0]) >> + { >> + if (templ [0] == '*') >> + { >> + templ++; >> + continue; >> + } >> + if (str [0] == 0) >> + return 0; >> + tpos = strchr (templ, '*'); >> + if (tpos != NULL) >> + tpos [0] = 0; >> + spos = strstr (str, templ); >> + str = spos + strlen (templ); >> + templ = tpos; >> + if (tpos != NULL) >> + tpos [0] = '*'; >> + if (spos == NULL) >> + return 0; >> + } >> + return 1; >> +} >> + >> +/* Loads ignore definitions from the file specified by -ftsan-ignore=filename. >> + Ignore files have the following format: >> + >> +# This is a comment - ignored >> + >> +# The below line says to not instrument memory accesses >> +# in all functions that match 'std*string*insert' >> +fun:std*string*insert >> + >> +# The below line says to not instrument memory accesses >> +# in the function called 'foobar' *and* in all functions >> +# that it calls recursively >> +fun_r:foobar >> + >> +# The below line says to not create superblocks >> +# in the function called 'barbaz' >> +fun_hist:barbaz >> + >> +# Ignore all functions in the source file >> +src:atomic.c >> + >> +# Everything else is uninteresting for us (e.g. obj:) >> +*/ >> +static void >> +ignore_load (void) >> +{ >> + FILE *f; >> + char *line; >> + size_t linesz; >> + ssize_t sz; >> + char buf [PATH_MAX]; >> + >> + if (flag_tsan_ignore == NULL || flag_tsan_ignore [0] == 0) >> + return; >> + >> + f = fopen (flag_tsan_ignore, "r"); >> + if (f == NULL) >> + { >> + /* Try to open it relative to main_input_filename. */ >> + strncpy (buf, main_input_filename, sizeof (buf)); >> + buf [sizeof (buf) - 1] = 0; >> + line = strrchr (buf, '/'); >> + if (line != NULL) >> + { >> + line++; >> + strncpy (line, flag_tsan_ignore, sizeof (buf) - (line - buf)); >> + buf [sizeof (buf) - 1] = 0; >> + f = fopen (buf, "r"); >> + } >> + } >> + if (f == NULL) >> + { >> + printf ("failed to open ignore file '%s'\n", flag_tsan_ignore); >> + exit (1); >> + } >> + >> + line = 0; >> + linesz = 0; >> + while ((sz = getline (&line, &linesz, f)) != -1) >> + { >> + if (sz == 0) >> + continue; >> + /* strip line terminator */ >> + if (line [sz-1] == '\r' || line [sz-1] == '\n') >> + line [sz-1] = 0; >> + if (strncmp (line, "src:", sizeof ("src:")-1) == 0) >> + ignore_append (tsan_ignore_func, line + sizeof ("src:")-1); >> + else if (strncmp (line, "fun:", sizeof ("fun:")-1) == 0) >> + ignore_append (tsan_ignore_mop, line + sizeof ("fun:")-1); >> + else if (strncmp (line, "fun_r:", sizeof ("fun_r:")-1) == 0) >> + ignore_append (tsan_ignore_rec, line + sizeof ("fun_r:")-1); >> + else if (strncmp (line, "fun_hist:", sizeof ("fun_hist:")-1) == 0) >> + ignore_append (tsan_ignore_hist, line + sizeof ("fun_hist:")-1); >> + /* other lines are not interesting */ >> + } >> + >> + free (line); >> + fclose (f); >> +} >> + >> +/* Returns ignore status for the current function */ >> +static enum tsan_ignore_e >> +tsan_ignore (void) >> +{ >> + const char *func_name; >> + const char *src_name; >> + struct tsan_ignore_desc_t *desc; >> + >> + if (ignore_init == 0) >> + { >> + ignore_load (); >> + ignore_init = 1; >> + } >> + >> + src_name = expand_location(cfun->function_start_locus).file; >> + if (src_name == NULL) >> + src_name = ""; >> + >> + func_name = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (cfun->decl)); >> + /* Ignore all functions starting with __tsan_ - intended for testing */ >> + if (strncmp (func_name, RTL_PERFIX, sizeof (RTL_PERFIX) - 1) == 0) >> + return tsan_ignore_func; >> + >> + for (desc = ignore_head; desc; desc = desc->next) >> + { >> + if (desc->type == tsan_ignore_func) >> + { >> + if (ignore_match (desc->name, src_name)) >> + return desc->type; >> + } >> + else if (ignore_match (desc->name, func_name)) >> + return desc->type; >> + } >> + return tsan_ignore_none; >> +} >> + >> +static const char * >> +decl_name (tree decl) >> +{ >> + tree id; >> + const char *name; >> + >> + if (decl != 0 && DECL_P (decl)) >> + { >> + id = DECL_NAME (decl); >> + if (id != NULL) >> + { >> + name = IDENTIFIER_POINTER (id); >> + if (name != NULL) >> + return name; >> + } >> + } >> + return "<unknown>"; >> +} >> + >> +/* Builds either (__tsan_shadow_stack += 1) or (__tsan_shadow_stack -= 1) expression >> + depending on 'do_dec' parameter. Appends the result to seq. */ >> +static void >> +build_stack_op (gimple_seq *seq, bool do_dec) >> +{ >> + tree op_size; >> + double_int op_size_cst; >> + unsigned long long size_val; >> + unsigned long long size_valhi; >> + tree op_expr; >> + tree assign; >> + tree rtl_stack; >> + gimple_seq s; >> + >> + op_size = TYPE_SIZE (ptr_type_node); >> + op_size_cst = tree_to_double_int (op_size); >> + size_val = op_size_cst.low / BITS_PER_UNIT; >> + size_valhi = 0; >> + if (do_dec) >> + { >> + size_val = -size_val; >> + size_valhi = -1; >> + } >> + op_size = build_int_cst_wide (sizetype, size_val, size_valhi); >> + rtl_stack = shadow_stack_def (); >> + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, rtl_stack, op_size); >> + assign = build2 (MODIFY_EXPR, ptr_type_node, rtl_stack, op_expr); >> + s = NULL; >> + force_gimple_operand (assign, &s, true, NULL_TREE); >> + gimple_seq_add_seq (seq, s); >> +} >> + >> +/* Builds either (__tsan_thread_ignore += 1) or (__tsan_thread_ignore -= 1) expression >> + depending on op parameter. Stores the result in seq. */ >> +static void >> +build_rec_ignore_op (gimple_seq *seq, enum tree_code op) >> +{ >> + tree rec_expr; >> + gimple_seq rec_inc; >> + gimple rec_assign; >> + tree rtl_ignore; >> + >> + rtl_ignore = thread_ignore_def (); >> + rec_expr = build2 (op, integer_type_node, rtl_ignore, integer_one_node); >> + rec_inc = NULL; >> + rec_expr = force_gimple_operand (rec_expr, &rec_inc, true, NULL_TREE); >> + rec_assign = gimple_build_assign (rtl_ignore, rec_expr); >> + gimple_seq_add_seq (seq, rec_inc); >> + gimple_seq_add_stmt (seq, rec_assign); >> +} >> + >> +/* Build the following gimple sequence: >> + __tsan_shadow_stack [-1] = __builtin_return_address (0); >> + Stores the result in seq. */ >> +static void >> +build_stack_assign (gimple_seq *seq) >> +{ >> + tree pc_addr; >> + tree op_size; >> + tree op_expr; >> + tree stack_op; >> + tree assign; >> + tree rtl_retaddr; >> + >> + rtl_retaddr = implicit_built_in_decls [BUILT_IN_RETURN_ADDRESS]; >> + pc_addr = build_call_expr (rtl_retaddr, 1, integer_zero_node); >> + op_size = build_int_cst_wide (sizetype, -(POINTER_SIZE / BITS_PER_UNIT), -1); >> + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, >> + shadow_stack_def (), op_size); >> + stack_op = build1 (INDIRECT_REF, ptr_type_node, op_expr); >> + assign = build2 (MODIFY_EXPR, ptr_type_node, stack_op, pc_addr); >> + force_gimple_operand (assign, seq, true, NULL_TREE); >> +} >> + >> +/* Builds the following gimple sequence: >> + __tsan_handle_mop (&expr, (is_sblock | (is_store << 1) | ((sizeof (expr)-1) << 2) >> + The result is stored in gseq. */ >> +static void >> +instr_mop (tree expr, int is_store, int is_sblock, gimple_seq *gseq) >> +{ >> + tree addr_expr; >> + tree expr_type; >> + unsigned size; >> + unsigned flags; >> + tree flags_expr; >> + tree call_expr; >> + >> + gcc_assert (gseq != 0 && *gseq == 0); >> + gcc_assert (is_gimple_addressable (expr)); >> + >> + addr_expr = build_addr (expr, current_function_decl); >> + expr_type = TREE_TYPE (expr); >> + while (TREE_CODE (expr_type) == ARRAY_TYPE) >> + expr_type = TREE_TYPE (expr_type); >> + size = TREE_INT_CST_LOW (TYPE_SIZE (expr_type)); >> + size = size / BITS_PER_UNIT; >> + if (size > MAX_MOP_BYTES) >> + size = MAX_MOP_BYTES; >> + size -= 1; >> + flags = ((!!is_sblock << 0) + (!!is_store << 1) + (size << 2)); >> + flags_expr = build_int_cst (unsigned_type_node, flags); >> + call_expr = build_call_expr (rtl_mop_def (), 2, addr_expr, flags_expr); >> + force_gimple_operand (call_expr, gseq, true, 0); >> +} >> + >> +/* Builds the following gimple sequence: >> + int is_store = (expr != rhs); // the temp is not actually introduced >> + __tsan_handle_mop (&expr, (is_sblock | (is_store << 1) | ((sizeof (expr)-1) << 2) >> + The result is stored in gseq. */ >> +static void >> +instr_vptr_store (tree expr, tree rhs, int is_sblock, gimple_seq *gseq) >> +{ >> + tree expr_ptr; >> + tree addr_expr; >> + tree expr_type; >> + tree expr_size; >> + double_int size; >> + unsigned flags; >> + tree flags_expr; >> + gimple_seq flags_seq; >> + gimple collect; >> + tree is_store_expr; >> + >> + expr_ptr = build_addr (expr, current_function_decl); >> + addr_expr = force_gimple_operand (expr_ptr, gseq, true, NULL_TREE); >> + expr_type = TREE_TYPE (expr); >> + while (TREE_CODE (expr_type) == ARRAY_TYPE) >> + expr_type = TREE_TYPE (expr_type); >> + expr_size = TYPE_SIZE (expr_type); >> + size = tree_to_double_int (expr_size); >> + gcc_assert (size.high == 0 && size.low != 0); >> + if (size.low > 128) >> + size.low = 128; >> + size.low = (size.low / 8) - 1; >> + flags = ((!!is_sblock << 0) + (size.low << 2)); >> + flags_expr = build_int_cst (unsigned_type_node, flags); >> + is_store_expr = build2 (NE_EXPR, integer_type_node, >> + build1 (VIEW_CONVERT_EXPR, size_type_node, expr), >> + build1 (VIEW_CONVERT_EXPR, size_type_node, rhs)); >> + is_store_expr = build2 (LSHIFT_EXPR, integer_type_node, >> + is_store_expr, integer_one_node); >> + flags_expr = build2 (BIT_IOR_EXPR, integer_type_node, >> + is_store_expr, flags_expr); >> + flags_seq = 0; >> + flags_expr = force_gimple_operand (flags_expr, &flags_seq, true, NULL_TREE); >> + gimple_seq_add_seq (gseq, flags_seq); >> + collect = gimple_build_call ( >> + rtl_mop_def (), 2, addr_expr, flags_expr); >> + gimple_seq_add_stmt (gseq, collect); >> +} >> + >> +/* Builds gimple sequences that must be inserted at function entry (pre) >> + and before function exit (post). */ >> +static void >> +instr_func (gimple_seq *pre, gimple_seq *post) >> +{ >> + /* In this case we need no instrumentation for the function */ >> + if (func_calls == 0 && func_mops == 0) >> + return; >> + >> + if (func_ignore != tsan_ignore_rec) >> + { >> + build_stack_assign (pre); >> + build_stack_op (pre, false); >> + build_stack_op (post, true); >> + } >> + >> + if (func_ignore == tsan_ignore_rec && func_calls != 0) >> + { >> + build_rec_ignore_op (pre, PLUS_EXPR); >> + build_rec_ignore_op (post, MINUS_EXPR); >> + } >> +} >> + >> +/* Sets location for all gimples in the seq. */ >> +static void >> +set_location (gimple_seq seq, location_t loc) >> +{ >> + gimple_seq_node n; >> + >> + for (n = gimple_seq_first (seq); n != NULL; n = n->next) >> + gimple_set_location (n->stmt, loc); >> +} >> + >> +/* Check as to whether expr refers to a store to vptr. */ >> +static tree >> +is_dtor_vptr_store (gimple stmt, tree expr, int is_store) >> +{ >> + if (is_store == 1 >> + && TREE_CODE (expr) == COMPONENT_REF >> + && gimple_assign_single_p (stmt) >> + && strcmp (decl_name (cfun->decl), "__base_dtor ") == 0) >> + { >> + tree comp = expr->exp.operands [0]; >> + while (TREE_CODE (comp) == COMPONENT_REF) >> + comp = comp->exp.operands [0]; >> + if (TREE_CODE (comp) == INDIRECT_REF || TREE_CODE (comp) == MEM_REF) >> + { >> + comp = comp->exp.operands [0]; >> + if (TREE_CODE (comp) == SSA_NAME) >> + comp = SSA_NAME_VAR (comp); >> + if (strcmp (decl_name (comp), "this") == 0) >> + { >> + tree field = expr->exp.operands [1]; >> + if (TREE_CODE (field) == FIELD_DECL >> + && strncmp (decl_name (field), >> + "_vptr.", sizeof ("_vptr.") - 1) == 0) >> + return gimple_assign_rhs1 (stmt); >> + } >> + } >> + } >> + return 0; >> +} >> + >> +/* Checks as to whether expr refers to a read from vtlb. >> + Vtlbs are immutable, so don't bother to instrument them. */ >> +static int >> +is_vtbl_read (tree expr, int is_store) >> +{ >> + /* We may not instrument reads from vtbl, because the data is constant. >> + vtbl read is of the form: >> + gimple_assign <component_ref, D.2133, x->_vptr.X, NULL> >> + gimple_assign <indirect_ref, D.2134, *D.2133, NULL> >> + or: >> + gimple_assign <component_ref, D.2133, x->_vptr.X, NULL> >> + gimple_assign <pointer_plus_expr, D.2135, D.2133, 8> >> + gimple_assign <indirect_ref, D.2136, *D.2135, NULL> */ >> + >> + if (is_store == 0 >> + && TREE_CODE (expr) == INDIRECT_REF) >> + { >> + tree ref_target = expr->exp.operands [0]; >> + if (TREE_CODE (ref_target) == SSA_NAME) >> + { >> + gimple ref_stmt = ref_target->ssa_name.def_stmt; >> + if (gimple_code (ref_stmt) == GIMPLE_ASSIGN) >> + { >> + if (gimple_expr_code (ref_stmt) == POINTER_PLUS_EXPR) >> + { >> + tree tmp = ref_stmt->gsmem.op [1]; >> + if (TREE_CODE (tmp) == SSA_NAME >> + && gimple_code (tmp->ssa_name.def_stmt) == GIMPLE_ASSIGN) >> + ref_stmt = tmp->ssa_name.def_stmt; >> + } >> + if (gimple_expr_code (ref_stmt) == COMPONENT_REF >> + && gimple_assign_single_p (ref_stmt)) >> + { >> + tree comp_expr = ref_stmt->gsmem.op [1]; >> + tree field_expr = comp_expr->exp.operands [1]; >> + if (TREE_CODE (field_expr) == FIELD_DECL >> + && strncmp (decl_name (field_expr), >> + "_vptr.", sizeof ("_vptr.") - 1) == 0) >> + return 1; >> + } >> + } >> + } >> + } >> + >> + return 0; >> +} >> + >> +/* Checks as to whether expr refers to constant var/field/param. >> + Don't bother to instrument them. */ >> +static int >> +is_load_of_const (tree expr, int is_store) >> +{ >> + if (is_store == 0) >> + { >> + if (TREE_CODE (expr) == COMPONENT_REF) >> + expr = expr->exp.operands [1]; >> + if (TREE_CODE (expr) == VAR_DECL >> + || TREE_CODE (expr) == PARM_DECL >> + || TREE_CODE (expr) == FIELD_DECL) >> + { >> + if (TREE_READONLY (expr)) >> + return 1; >> + } >> + } >> + return 0; >> +} >> + >> +static void >> +handle_expr (gimple stmt, gimple_stmt_iterator gsi, >> + tree expr, int is_store, VEC (mop_desc_t, heap) **mop_list) >> +{ >> + enum tree_code tcode; >> + struct mop_desc_t mop; >> + unsigned fld_off; >> + unsigned fld_size; >> + >> + /* map SSA name to real name */ >> + if (TREE_CODE (expr) == SSA_NAME) >> + expr = SSA_NAME_VAR (expr); >> + >> + tcode = TREE_CODE (expr); >> + >> + /* Below are things we do NOT want to instrument. */ >> + if (func_ignore & (tsan_ignore_mop | tsan_ignore_rec)) >> + { >> + return; >> + } >> + else if (TREE_CODE_CLASS (tcode) == tcc_constant) >> + { >> + /* various constant literals */ >> + return; >> + } >> + else if (TREE_CODE_CLASS (tcode) == tcc_declaration >> + && DECL_ARTIFICIAL (expr)) >> + { >> + /* compiler-emitted artificial variables */ >> + return; >> + } >> + if (tcode == RESULT_DECL) >> + { >> + /* store to function result */ >> + return; >> + } >> + else if (tcode == VAR_DECL >> + && TREE_ADDRESSABLE (expr) == 0 >> + && TREE_STATIC (expr) == 0) >> + { >> + /* the var does not live in memory -> no possibility of races */ >> + return; >> + } >> + else if (TREE_CODE (TREE_TYPE (expr)) == RECORD_TYPE) >> + { >> + /* TODO (dvyukov): implement me */ >> + return; >> + } >> + else if (tcode == CONSTRUCTOR) >> + { >> + /* TODO (dvyukov): implement me */ >> + return; >> + } >> + else if (tcode == PARM_DECL) >> + { >> + /* TODO (dvyukov): implement me */ >> + return; >> + } >> + else if (is_load_of_const (expr, is_store)) >> + { >> + /* load of a const variable/parameter/field */ >> + return; >> + } >> + else if (is_vtbl_read (expr, is_store)) >> + { >> + /* vtbl read */ >> + return; >> + } >> + else if (tcode == COMPONENT_REF) >> + { >> + tree field = expr->exp.operands [1]; >> + if (TREE_CODE (field) == FIELD_DECL) >> + { >> + fld_off = field->field_decl.bit_offset->int_cst.int_cst.low; >> + fld_size = field->decl_common.size->int_cst.int_cst.low; >> + if (((fld_off % BITS_PER_UNIT) != 0) >> + || ((fld_size % BITS_PER_UNIT) != 0)) >> + { >> + /* As of now it crashes compilation. >> + TODO (dvyukov): handle bit-fields as if touching the whole field */ >> + return; >> + } >> + } >> + } >> + >> + /* TODO (dvyukov): handle other cases >> + (FIELD_DECL, MEM_REF, ARRAY_RANGE_REF, TARGET_MEM_REF, ADDR_EXPR) */ >> + if (tcode != ARRAY_REF >> + && tcode != VAR_DECL >> + && tcode != COMPONENT_REF >> + && tcode != INDIRECT_REF >> + && tcode != MEM_REF) >> + return; >> + >> + mop.is_call = 0; >> + mop.gsi = gsi; >> + mop.expr = expr; >> + mop.dtor_vptr_expr = is_dtor_vptr_store (stmt, expr, is_store); >> + mop.is_store = is_store; >> + VEC_safe_push (mop_desc_t, heap, *mop_list, &mop); >> +} >> + >> +static void >> +handle_gimple (gimple_stmt_iterator gsi, VEC (mop_desc_t, heap) **mop_list) >> +{ >> + unsigned i; >> + struct mop_desc_t mop; >> + gimple stmt; >> + enum gimple_code gcode; >> + location_t loc; >> + tree rhs; >> + tree lhs; >> + >> + stmt = gsi_stmt (gsi); >> + gcode = gimple_code (stmt); >> + if (gcode >= LAST_AND_UNUSED_GIMPLE_CODE) >> + return; >> + >> + loc = gimple_location (stmt); >> + >> + switch (gcode) >> + { >> + /* TODO (dvyukov): handle GIMPLE_COND (can it access memmory?) */ >> + case GIMPLE_CALL: >> + { >> + func_calls += 1; >> + /* Handle call arguments as loads */ >> + for (i = 0; i < gimple_call_num_args (stmt); i++) >> + { >> + rhs = gimple_call_arg (stmt, i); >> + handle_expr (stmt, gsi, rhs, 0, mop_list); >> + } >> + >> + memset (&mop, 0, sizeof (mop)); >> + mop.is_call = 1; >> + VEC_safe_push (mop_desc_t, heap, *mop_list, &mop); >> + >> + /* Handle assignment lhs as store */ >> + lhs = gimple_call_lhs (stmt); >> + if (lhs != 0) >> + handle_expr (stmt, gsi, lhs, 1, mop_list); >> + >> + break; >> + } >> + >> + case GIMPLE_ASSIGN: >> + { >> + /* Handle assignment lhs as store */ >> + lhs = gimple_assign_lhs (stmt); >> + handle_expr (stmt, gsi, lhs, 1, mop_list); >> + >> + /* Handle operands as loads */ >> + for (i = 1; i < gimple_num_ops (stmt); i++) >> + { >> + rhs = gimple_op (stmt, i); >> + handle_expr (stmt, gsi, rhs, 0, mop_list); >> + } >> + break; >> + } >> + >> + case GIMPLE_BIND: >> + { >> + gcc_assert (!"there should be no GIMPLE_BIND on this level"); >> + break; >> + } >> + >> + default: >> + break; >> + } >> +} >> + >> +/* Instruments single basic block. */ >> +static void >> +instrument_bblock (struct bb_data_t *bbd, basic_block bb) >> +{ >> + int ix; >> + int is_sblock; >> + gimple_stmt_iterator gsi; >> + struct mop_desc_t *mop; >> + gimple stmt; >> + location_t loc; >> + expanded_location eloc; >> + gimple_seq instr_seq; >> + >> + /* Iterate over all gimples and collect interesting mops into mop_list. */ >> + VEC_free (mop_desc_t, heap, mop_list); >> + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) >> + { >> + handle_gimple (gsi, &mop_list); >> + } >> + >> + mop = 0; >> + for (ix = 0; VEC_iterate (mop_desc_t, mop_list, ix, mop); ix += 1) >> + { >> + if (mop->is_call != 0) >> + { >> + /* After a function call we must start a brand new sblock, >> + because the function can contain synchronization. */ >> + bbd->has_sb = 0; >> + continue; >> + } >> + >> + func_mops += 1; >> + stmt = gsi_stmt (mop->gsi); >> + loc = gimple_location (stmt); >> + eloc = expand_location (loc); >> + >> + /* Check as to whether we may not set sblock flag >> + for the access */ >> + is_sblock = (bbd->has_sb == 0 >> + || !(eloc.file != 0 >> + && bbd->sb_file != 0 >> + && strcmp (eloc.file, bbd->sb_file) == 0 >> + && eloc.line >= bbd->sb_line_min >> + && eloc.line <= bbd->sb_line_max)); >> + >> + if (func_ignore == tsan_ignore_hist) >> + is_sblock = 0; >> + >> + if (is_sblock) >> + { >> + /* Start new sblock with new source info. */ >> + bbd->has_sb = 1; >> + bbd->sb_file = eloc.file; >> + bbd->sb_line_min = eloc.line; >> + bbd->sb_line_max = eloc.line + SBLOCK_SIZE; >> + } >> + >> + instr_seq = 0; >> + if (mop->dtor_vptr_expr == 0) >> + instr_mop (mop->expr, mop->is_store, is_sblock, &instr_seq); >> + else >> + instr_vptr_store (mop->expr, mop->dtor_vptr_expr, is_sblock, &instr_seq); >> + gcc_assert (instr_seq != 0); >> + set_location (instr_seq, loc); >> + /* Instrumentation for assignment of a function result >> + must be inserted after the call. Instrumentation for >> + reads of function arguments must be inserted before the call. >> + That's because the call can contain synchronization. */ >> + if (is_gimple_call (stmt) && mop->is_store == 1) >> + gsi_insert_seq_after (&mop->gsi, instr_seq, GSI_NEW_STMT); >> + else >> + gsi_insert_seq_before (&mop->gsi, instr_seq, GSI_SAME_STMT); >> + } >> +} >> + >> +/* Instruments all interesting memory accesses in the function */ >> +static void >> +instrument_mops (void) >> +{ >> + int sb_line_min; >> + int sb_line_max; >> + int bb_cnt; >> + int eidx; >> + basic_block bb; >> + basic_block entry_bb; >> + basic_block cur_bb; >> + basic_block any_bb; >> + struct bb_data_t *pred; >> + struct bb_data_t *succ; >> + struct bb_data_t *bb_data; >> + struct bb_data_t *bbd; >> + edge entry_edge; >> + edge e; >> + >> + /* The function does breadth-first traversal of CFG. >> + BB is visited preferably if all its predecessors are visited. >> + Such order is required to properly mark super-blocks. >> + The idea behind super-blocks is as follows. >> + If several memory accesses happen within SBLOCK_SIZE source code lines >> + from each other, then we only mark the first access as SBLOCK. >> + This allows runtime library to memorize stack trace >> + only for the first access and do not memorize for others. >> + This significantly reduces memory consumption in exchange for slightly >> + imprecise stack traces for previous accesses. */ >> + >> + /* First, mark all blocks as not visited, and entry block as candidate. */ >> + bb_cnt = cfun->cfg->x_n_basic_blocks; >> + bb_data = (struct bb_data_t*) xcalloc (bb_cnt, sizeof (struct bb_data_t)); >> + entry_bb = ENTRY_BLOCK_PTR; >> + entry_edge = single_succ_edge (entry_bb); >> + entry_bb = entry_edge->dest; >> + bb = 0; >> + FOR_EACH_BB (bb) >> + { >> + bb_data [bb->index].state = (bb == entry_bb) ? bb_candidate : bb_not_visited; >> + } >> + >> + /* Until all blocks are visited. */ >> + for (; ; ) >> + { >> + cur_bb = 0; >> + any_bb = 0; >> + /* Look for a candidate with all visited predecessors. */ >> + FOR_EACH_BB (bb) >> + { >> + bbd = &bb_data [bb->index]; >> + if (bbd->state == bb_candidate) >> + { >> + cur_bb = bb; >> + any_bb = bb; >> + e = 0; >> + for (eidx = 0; VEC_iterate (edge, bb->preds, eidx, e); eidx++) >> + { >> + pred = &bb_data [e->src->index]; >> + if (pred->state != bb_visited) >> + { >> + cur_bb = 0; >> + break; >> + } >> + } >> + } >> + if (cur_bb != 0) >> + break; >> + } >> + /* All blocks are visited. */ >> + if (any_bb == 0) >> + break; >> + /* If no blocks with all visited predecessors, choose any candidate. >> + Must be a loop. */ >> + cur_bb = cur_bb ? cur_bb : any_bb; >> + bbd = &bb_data [cur_bb->index]; >> + gcc_assert (bbd->state == bb_candidate); >> + bbd->state = bb_visited; >> + >> + /* Iterate over all predecessors and merge their sblock info. */ >> + e = 0; >> + for (eidx = 0; VEC_iterate (edge, cur_bb->preds, eidx, e); eidx++) >> + { >> + pred = &bb_data [e->src->index]; >> + if ((pred->state != bb_visited) >> + || (pred->has_sb == 0) >> + || (pred == bbd)) >> + { >> + /* If there is a not visited predecessor, >> + or a predecessor with no active sblock info, >> + or a self-loop, then we will have to start >> + a brand new sblock on next memory access. */ >> + bbd->has_sb = 0; >> + break; >> + } >> + else if (bbd->has_sb == 0) >> + { >> + /* If it's a first predecessor, just copy the info. */ >> + bbd->has_sb = 1; >> + bbd->sb_file = pred->sb_file; >> + bbd->sb_line_min = pred->sb_line_min; >> + bbd->sb_line_max = pred->sb_line_max; >> + } >> + else >> + { >> + /* Otherwise, find the interception >> + between two sblock descriptors. */ >> + bbd->has_sb = 0; >> + if (bbd->sb_file != 0 && pred->sb_file != 0 >> + && strcmp (bbd->sb_file, pred->sb_file) == 0) >> + { >> + sb_line_min = MAX (bbd->sb_line_min, pred->sb_line_min); >> + sb_line_max = MIN (bbd->sb_line_max, pred->sb_line_max); >> + if (sb_line_min <= sb_line_max) >> + { >> + bbd->has_sb = 1; >> + bbd->sb_line_min = sb_line_min; >> + bbd->sb_line_max = sb_line_max; >> + } >> + } >> + /* No interception, have to start new sblock. */ >> + if (bbd->has_sb == 0) >> + break; >> + } >> + } >> + >> + /* Finally, instrument the block. */ >> + instrument_bblock (bbd, cur_bb); >> + >> + /* Mark all successors as candidates. */ >> + for (eidx = 0; VEC_iterate (edge, cur_bb->succs, eidx, e); eidx++) >> + { >> + succ = &bb_data [e->dest->index]; >> + if (succ->state == bb_not_visited) >> + succ->state = bb_candidate; >> + } >> + } >> +} >> + >> +/* Instruments function entry and exit, if necessary. */ >> +static void >> +instrument_function (void) >> +{ >> + location_t loc; >> + gimple_seq pre_func_seq; >> + gimple_seq post_func_seq; >> + basic_block entry_bb; >> + basic_block first_bb; >> + basic_block bb; >> + edge entry_edge; >> + gimple_stmt_iterator first_gsi; >> + gimple_stmt_iterator gsi; >> + gimple_stmt_iterator gsi2; >> + gimple first_stmt; >> + gimple stmt; >> + >> + pre_func_seq = 0; >> + post_func_seq = 0; >> + instr_func (&pre_func_seq, &post_func_seq); >> + >> + if (pre_func_seq != 0) >> + { >> + /* Insert new BB before the first BB. */ >> + entry_bb = ENTRY_BLOCK_PTR; >> + entry_edge = single_succ_edge (entry_bb); >> + first_bb = entry_edge->dest; >> + first_gsi = gsi_start_bb (first_bb); >> + if (!gsi_end_p (first_gsi)) >> + { >> + first_stmt = gsi_stmt (first_gsi); >> + loc = gimple_location (first_stmt); >> + set_location (pre_func_seq, loc); >> + } >> + entry_bb = split_edge (entry_edge); >> + gsi = gsi_start_bb (entry_bb); >> + gsi_insert_seq_after (&gsi, pre_func_seq, GSI_NEW_STMT); >> + } >> + >> + if (post_func_seq != 0) >> + { >> + /* Find all function exits. */ >> + FOR_EACH_BB (bb) >> + { >> + gsi2 = gsi_start_bb (bb); >> + for (; ; ) >> + { >> + gsi = gsi2; >> + if (gsi_end_p (gsi)) >> + break; >> + gsi_next (&gsi2); >> + >> + stmt = gsi_stmt (gsi); >> + loc = gimple_location (stmt); >> + >> + if (gimple_code (stmt) == GIMPLE_RETURN) >> + { >> + set_location (post_func_seq, loc); >> + gsi_insert_seq_before (&gsi, post_func_seq, GSI_SAME_STMT); >> + } >> + } >> + } >> + } >> +} >> + >> +static unsigned >> +tsan_pass (void) >> +{ >> + if (errorcount != 0 || sorrycount != 0) >> + return 0; >> + >> + func_ignore = tsan_ignore (); >> + if (func_ignore == tsan_ignore_func) >> + return 0; >> + >> + func_calls = 0; >> + func_mops = 0; >> + >> + instrument_mops (); >> + instrument_function (); >> + >> + return 0; >> +} >> + >> +static bool >> +tsan_gate (void) >> +{ >> + return flag_tsan != 0; >> +} >> + >> +struct gimple_opt_pass pass_tsan = {{ >> + GIMPLE_PASS, >> + "tsan", /* name */ >> + tsan_gate, /* gate */ >> + tsan_pass, /* execute */ >> + NULL, /* sub */ >> + NULL, /* next */ >> + 0, /* static_pass_number */ >> + TV_NONE, /* tv_id */ >> + PROP_trees | PROP_cfg, /* properties_required */ >> + 0, /* properties_provided */ >> + 0, /* properties_destroyed */ >> + 0, /* todo_flags_start */ >> + TODO_dump_cgraph | TODO_dump_func | TODO_verify_all >> + | TODO_update_ssa | TODO_update_address_taken /* todo_flags_finish */ >> +}}; >> + >> >> Property changes on: gcc/tree-tsan.c >> ___________________________________________________________________ >> Added: svn:eol-style >> + LF >> >> Index: gcc/tree-pass.h >> =================================================================== >> --- gcc/tree-pass.h (revision 180522) >> +++ gcc/tree-pass.h (working copy) >> @@ -352,6 +352,7 @@ >> >> extern struct gimple_opt_pass pass_mudflap_1; >> extern struct gimple_opt_pass pass_mudflap_2; >> +extern struct gimple_opt_pass pass_tsan; >> extern struct gimple_opt_pass pass_lower_cf; >> extern struct gimple_opt_pass pass_refactor_eh; >> extern struct gimple_opt_pass pass_lower_eh; >> Index: gcc/testsuite/gcc.dg/tsan-ignore.ignore >> =================================================================== >> --- gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) >> +++ gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) >> @@ -0,0 +1,7 @@ >> +#comment >> +fun:foo >> +fun:*bar >> +fun:baz* >> +fun:*bla* >> +fun:x*x >> +src:tsan-ignore.h >> Index: gcc/testsuite/gcc.dg/tsan.h >> =================================================================== >> --- gcc/testsuite/gcc.dg/tsan.h (revision 0) >> +++ gcc/testsuite/gcc.dg/tsan.h (revision 0) >> @@ -0,0 +1,87 @@ >> +/* Helper declarations and functions for ThreadSanitizer instrumentation (-ftsan) testing */ >> + >> +int printf (char *str, ...); >> +void exit (int); >> + >> +/* Variables referenced by the instrumentation */ >> +__thread void **__tsan_shadow_stack; >> +__thread int __tsan_thread_ignore; >> + >> +/* Local helper vars */ >> +__thread void *shadow_stack[1024]; >> +__thread int mop_expect; >> +__thread int mop_depth; >> +__thread void* mop_addr; >> +__thread unsigned long long mop_pc; >> +__thread unsigned mop_flags; >> + >> +/* Setups shadow stack var (not instrumented) */ >> +void __attribute__ ((constructor)) >> +__tsan_init (void) >> +{ >> + __tsan_shadow_stack = shadow_stack; >> +} >> + >> +/* Declare that we expect an instrumented memory access (not instrumented). >> + depth - stack depth of the mop (0 - main, 1 - func called from main and so on). >> + addr - memory access address. >> + is_store - store/load. >> + is_sblock - superblock flag of the access. >> + size - memory access size in bytes. */ >> +void >> +__tsan_expect_mop (int depth, void *addr, int is_store, int is_sblock, int size) >> +{ >> + if (mop_expect) >> + { >> + printf ("missed mop: addr=%p pc=%d\n", mop_addr, mop_pc); >> + exit (1); >> + } >> + >> + mop_expect = 1; >> + mop_depth = depth; >> + mop_addr = addr; >> + mop_pc = (unsigned long long)__builtin_return_address(0); >> + mop_flags = !!is_sblock | (!!is_store << 1) | ((size - 1) << 2); >> +} >> + >> +/* Memory access function (referenced by instrumentation, not instrumented). */ >> +void >> +__tsan_handle_mop (void *addr, unsigned flags) >> +{ >> + unsigned long long pc; >> + int depth; >> + >> + printf ("mop: addr=%p flags=%x called from %p/%p\n", addr, flags, __builtin_return_address(1), __tsan_shadow_stack[-2]); >> + if (mop_expect == 0) >> + return; >> + >> + /* Verify parameters with what we expect. */ >> + >> + if (addr != mop_addr) >> + { >> + printf ("incorrect mop addr: %p/%p\n", addr, mop_addr); >> + exit (1); >> + } >> + >> + pc = (unsigned long long)__builtin_return_address(0); >> + if (pc < mop_pc - 100 || pc > mop_pc + 100) >> + { >> + printf ("incorrect mop pc: %p/%p\n", (void*)pc, (void*)mop_pc); >> + exit (1); >> + } >> + >> + depth = __tsan_shadow_stack - shadow_stack - 1; >> + if (depth != mop_depth) >> + { >> + printf ("incorrect mop depth: %d/%d\n", depth, mop_depth); >> + exit (1); >> + } >> + >> + if (flags != mop_flags) >> + { >> + printf ("incorrect mop flags: %x/%x\n", flags, mop_flags); >> + exit (1); >> + } >> + >> + mop_expect = 0; >> +} >> >> Property changes on: gcc/testsuite/gcc.dg/tsan.h >> ___________________________________________________________________ >> Added: svn:eol-style >> + LF >> >> Index: gcc/testsuite/gcc.dg/tsan-ignore.c >> =================================================================== >> --- gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) >> +++ gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) >> @@ -0,0 +1,49 @@ >> +/* { dg-do run } */ >> +/* { dg-options "-ftsan -ftsan-ignore=tsan-ignore.ignore" } */ >> +#include "tsan.h" >> +#include "tsan-ignore.h" >> + >> +/* Check ignore file handling. */ >> + >> +int >> +foo (int *p) >> +{ >> + p [0] = 1; >> +} >> + >> +int bar (int *p) >> +{ >> + p [0] = 1; >> +} >> + >> +int baz (int *p) >> +{ >> + p [0] = 1; >> +} >> + >> +int bla (int *p) >> +{ >> + p [0] = 1; >> +} >> + >> +int xxx (int *p) >> +{ >> + p [0] = 1; >> +} >> + >> +int >> +main (void) >> +{ >> + int p, x; >> + >> + __tsan_expect_mop(0, &p, 1, 1, sizeof(p)); >> + /* All these functions must be ignored. */ >> + foo (&x); >> + bar (&x); >> + baz (&x); >> + bla (&x); >> + xxx (&x); >> + in_tsan_ignore_header (&x); >> + p = 0; >> + return 0; >> +} >> >> Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.c >> ___________________________________________________________________ >> Added: svn:eol-style >> + LF >> >> Index: gcc/testsuite/gcc.dg/tsan-ignore.h >> =================================================================== >> --- gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) >> +++ gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) >> @@ -0,0 +1,5 @@ >> +int >> +in_tsan_ignore_header (int *p) >> +{ >> + p [0] = 1; >> +} >> >> Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.h >> ___________________________________________________________________ >> Added: svn:eol-style >> + LF >> >> Index: gcc/testsuite/gcc.dg/tsan-stack.c >> =================================================================== >> --- gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) >> +++ gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) >> @@ -0,0 +1,23 @@ >> +/* { dg-do run } */ >> +/* { dg-options "-ftsan" } */ >> +#include "tsan.h" >> + >> +/* Check shadow stack maintance. */ >> + >> +int >> +foobar (int *p) >> +{ >> + __tsan_expect_mop(1, p, 1, 1, sizeof(*p)); >> + p[0] = 1; >> +} >> + >> +int >> +main (void) >> +{ >> + int p; >> + >> + __tsan_expect_mop(0, &p, 1, 1, sizeof(p)); >> + p = 0; >> + foobar (&p); >> + return 0; >> +} >> >> Property changes on: gcc/testsuite/gcc.dg/tsan-stack.c >> ___________________________________________________________________ >> Added: svn:eol-style >> + LF >> >> Index: gcc/testsuite/gcc.dg/tsan-mop.c >> =================================================================== >> --- gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) >> +++ gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) >> @@ -0,0 +1,29 @@ >> +/* { dg-do run } */ >> +/* { dg-options "-ftsan" } */ >> +#include "tsan.h" >> + >> +/* Sanity check for memory accesses instrumentation. */ >> + >> +int >> +foobar (int *p) >> +{ >> + __tsan_expect_mop(1, p, 1, 1, sizeof(*p)); >> + p[0] = 1; >> + >> + __tsan_expect_mop(1, p, 1, 1, sizeof(*p)); >> + *p = 2; >> + >> + __tsan_expect_mop(1, (char*)p+3, 1, 1, 1); >> + *((char*)p+3) = 3; >> +} >> + >> +int >> +main (void) >> +{ >> + int p; >> + >> + __tsan_expect_mop(0, &p, 1, 1, sizeof(p)); >> + p = 0; >> + foobar (&p); >> + return 0; >> +} >> >> Property changes on: gcc/testsuite/gcc.dg/tsan-mop.c >> ___________________________________________________________________ >> Added: svn:eol-style >> + LF >> >> Index: gcc/common.opt >> =================================================================== >> --- gcc/common.opt (revision 180522) >> +++ gcc/common.opt (working copy) >> @@ -1547,6 +1547,14 @@ >> Common RejectNegative Report Var(flag_mudflap_ignore_reads) >> Ignore read operations when inserting mudflap instrumentation >> >> +ftsan >> +Common RejectNegative Report Var(flag_tsan) >> +Add ThreadSanitizer instrumentation >> + >> +ftsan-ignore= >> +Common RejectNegative Joined Var(flag_tsan_ignore) >> +-ftsan-ignore=filename ThreadSanitizer ignore file >> + >> fdce >> Common Var(flag_dce) Init(1) Optimization >> Use the RTL dead code elimination pass >> Index: gcc/Makefile.in >> =================================================================== >> --- gcc/Makefile.in (revision 180522) >> +++ gcc/Makefile.in (working copy) >> @@ -1494,6 +1494,7 @@ >> tree-streamer-out.o \ >> tree-tailcall.o \ >> tree-threadsafe-analyze.o \ >> + tree-tsan.o \ >> tree-vect-generic.o \ >> tree-vect-patterns.o \ >> tree-vect-data-refs.o \ >> @@ -2814,6 +2815,12 @@ >> $(C_TREE_H) $(C_COMMON_H) $(GIMPLE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) \ >> output.h langhooks.h tree-mudflap.h $(TM_H) coretypes.h \ >> $(GGC_H) gt-tree-mudflap.h $(TREE_PASS_H) $(DIAGNOSTIC_CORE_H) >> +tree-tsan.o : $(CONFIG_H) $(SYSTEM_H) $(TREE_H) $(TREE_INLINE_H) \ >> + $(GIMPLE_H) $(DIAGNOSTIC_H) langhooks.h \ >> + $(TM_H) coretypes.h $(TREE_DUMP_H) $(TREE_PASS_H) $(CGRAPH_H) $(GGC_H) \ >> + $(BASIC_BLOCK_H) $(FLAGS_H) $(FUNCTION_H) \ >> + $(TM_P_H) $(TREE_FLOW_H) $(DIAGNOSTIC_CORE_H) $(GIMPLE_H) tree-iterator.h \ >> + intl.h cfghooks.h output.h options.h c-family/c-common.h >> tree-pretty-print.o : tree-pretty-print.c $(CONFIG_H) $(SYSTEM_H) \ >> $(TREE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) $(TREE_FLOW_H) \ >> $(TM_H) coretypes.h tree-iterator.h $(SCEV_H) langhooks.h \ >> Index: gcc/passes.c >> =================================================================== >> --- gcc/passes.c (revision 180522) >> +++ gcc/passes.c (working copy) >> @@ -1420,6 +1420,7 @@ >> NEXT_PASS (pass_lower_resx); >> NEXT_PASS (pass_nrv); >> NEXT_PASS (pass_mudflap_2); >> + NEXT_PASS (pass_tsan); >> NEXT_PASS (pass_cleanup_cfg_post_optimizing); >> NEXT_PASS (pass_warn_function_noreturn); >> >> >> -- >> This patch is available for review at http://codereview.appspot.com/5303083 >> >> -- >> You received this message because you are subscribed to the Google Groups "C-compiler-team" group. >> To post to this group, send email to c-compiler-team@google.com. >> To unsubscribe from this group, send email to c-compiler-team+unsubscribe@google.com. >> For more options, visit this group at http://groups.google.com/a/google.com/group/c-compiler-team/?hl=en. >> >> > > -- > You received this message because you are subscribed to the Google Groups "C-compiler-team" group. > To post to this group, send email to c-compiler-team@google.com. > To unsubscribe from this group, send email to c-compiler-team+unsubscribe@google.com. > For more options, visit this group at http://groups.google.com/a/google.com/group/c-compiler-team/?hl=en. > >
Sign in to reply to this message.
On Fri, Oct 28, 2011 at 7:53 PM, Xinliang David Li <davidxl@google.com>wrote: > Dmitriy, I will review it next week. > > np we just want to make it into the next release if possible. > Do you have some runtime overhead data ? > > You may see some results in this paper: http://code.google.com/p/data-race-test/downloads/detail?name=ThreadSanitizer... (see Table 1) It is about llvm based instrumentation, but they have very similar performance.
Sign in to reply to this message.
Have not done with reviewing. This is the first batch. David http://codereview.appspot.com/5303083/diff/1/gcc/passes.c File gcc/passes.c (right): http://codereview.appspot.com/5303083/diff/1/gcc/passes.c#newcode1423 gcc/passes.c:1423: NEXT_PASS (pass_tsan); Move this to the same place as asan. Otherwise TARGET_MEM_REF won't be handled. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c File gcc/tree-tsan.c (right): http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode56 gcc/tree-tsan.c:56: The instrumentation module mainintains shadow call stacks s/mainitains/maintains/ http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode60 gcc/tree-tsan.c:60: Instrumentation for shadow stack maintainance is as follows: s/maintainance/maintenance/ http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode94 gcc/tree-tsan.c:94: #define RTL_STACK "__tsan_shadow_stack" Please change RTL_ prefix to TSAN_. It is confusing to use RTL_ http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode100 gcc/tree-tsan.c:100: enum tsan_ignore_e better to be tsan_ignore_type or tsan_ignore_kind. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode110 gcc/tree-tsan.c:110: enum bb_state_e A new empty line is needed. Same for other comments leading a decl, or function. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode110 gcc/tree-tsan.c:110: enum bb_state_e bb_state_e -->bb_state http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode119 gcc/tree-tsan.c:119: struct bb_data_t _t suffix is better removed. Same for other types with _t suffix. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode161 gcc/tree-tsan.c:161: tree __attribute__((weak)) Explain this. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode169 gcc/tree-tsan.c:169: extern __thread void **__tsan_shadow_stack; */ Need two white space before */. Same for other instances. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode182 gcc/tree-tsan.c:182: Better use varpool_get_node interface. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode186 gcc/tree-tsan.c:186: TREE_STATIC (def) = 1; Why mark TREE_STATIC (def) = 1? Should the variable be defined in tsan library? http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode189 gcc/tree-tsan.c:189: DECL_TLS_MODEL (def) = decl_default_tls_model (def); Check if targetm.have_tls -- though for those target, tsan won't be used. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode200 gcc/tree-tsan.c:200: { Refactor the code so that it can be shared with the above one. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode228 gcc/tree-tsan.c:228: { The name of the function is very confusing. Change it to get_tsan_mop_handler_decl or something like that. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode251 gcc/tree-tsan.c:251: /* Adds new ignore definition to the global list */ Add documentation on function parameters (in upper case) such as " TYPE is the ignore type, and NAME is the name of the function to be ignored. If there is return value, document it too. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode257 gcc/tree-tsan.c:257: desc = (struct tsan_ignore_desc_t*)xmalloc (sizeof (*desc)); Use XCNEW to clear. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode264 gcc/tree-tsan.c:264: /* Checks as to whether identifier 'str' matches template 'templ'. Use STR instead of 'str'. 'templ' --> TEMPL. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode291 gcc/tree-tsan.c:291: if (spos == NULL) Move the check up right after spos is computed. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode349 gcc/tree-tsan.c:349: printf ("failed to open ignore file '%s'\n", flag_tsan_ignore); Use error (..) http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode360 gcc/tree-tsan.c:360: if (line [sz-1] == '\r' || line [sz-1] == '\n') sz-1 --> sz - 1 Change other instances http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode391 gcc/tree-tsan.c:391: src_name = expand_location(cfun->function_start_locus).file; space before ( http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode413 gcc/tree-tsan.c:413: static const char * Missing documentation. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode443 gcc/tree-tsan.c:443: tree rtl_stack; Do not use rtl_ prefix. Same for other instances. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode459 gcc/tree-tsan.c:459: s = NULL; MODIFY_EXPR? directly use gimple_build_assign. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode725 gcc/tree-tsan.c:725: This is wrong. SSA_NAME expr should be skipped. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode730 gcc/tree-tsan.c:730: { remove {} Same for other one liners. Many conditions should be merged using || http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode751 gcc/tree-tsan.c:751: && TREE_STATIC (expr) == 0) If you want to skip local variables, check DECL_EXTERNAL attribute. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode783 gcc/tree-tsan.c:783: tree field = expr->exp.operands [1]; Do not directly reference tree's member like this. Use TREE_OPERAND http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode786 gcc/tree-tsan.c:786: fld_off = field->field_decl.bit_offset->int_cst.int_cst.low; Do not access tree like this. Use DECL_FIELD_OFFSET etc. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode844 gcc/tree-tsan.c:844: } Remove code handling arguments. THey should not have mem refs. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode854 gcc/tree-tsan.c:854: Remove lhs handling -- no need. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode877 gcc/tree-tsan.c:877: } Remove handling of GIMPLE_BIND http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode1003 gcc/tree-tsan.c:1003: { For topo order traversal, use inverted_post_order_compute and then handling bb s in that order. This will simplify the code a lot. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode1141 gcc/tree-tsan.c:1141: { Better way is to walk through all predecessors of EXIT_BLOCK_PTR. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode1167 gcc/tree-tsan.c:1167: return 0; No need for the above.
Sign in to reply to this message.
Fixes after davidxl review. The patch is for google/main branch. 2011-10-31 Dmitriy Vyukov <dvyukov@google.com> * gcc/doc/invoke.texi: * gcc/tree-tsan.c (enum tsan_ignore_type): (struct bb_data): (struct mop_desc): (struct tsan_ignore_desc): (lookup_name): (build_var_decl): (get_shadow_stack_decl): (get_thread_ignore_decl): (get_handle_mop_decl): (ignore_append): (ignore_match): (ignore_load): (tsan_ignore): (decl_name): (build_stack_op): (build_rec_ignore_op): (build_stack_assign): (instr_mop): (instr_vptr_store): (instr_func): (set_location): (is_dtor_vptr_store): (is_vtbl_read): (is_load_of_const): (handle_expr): (handle_gimple): (instrument_bblock): (instrument_mops): (instrument_function): (tsan_pass): (tsan_gate): * gcc/tree-pass.h: * gcc/testsuite/gcc.dg/tsan-ignore.ignore: * gcc/testsuite/gcc.dg/tsan.h (__tsan_init): (__tsan_expect_mop): (__tsan_handle_mop): * gcc/testsuite/gcc.dg/tsan-ignore.c (foo): (int bar): (int baz): (int bla): (int xxx): (main): * gcc/testsuite/gcc.dg/tsan-ignore.h (in_tsan_ignore_header): * gcc/testsuite/gcc.dg/tsan-stack.c (foobar): * gcc/testsuite/gcc.dg/tsan-mop.c: * gcc/common.opt: * gcc/Makefile.in: * gcc/passes.c: Index: gcc/doc/invoke.texi =================================================================== --- gcc/doc/invoke.texi (revision 180522) +++ gcc/doc/invoke.texi (working copy) @@ -308,6 +308,7 @@ -fdump-tree-ssa@r{[}-@var{n}@r{]} -fdump-tree-pre@r{[}-@var{n}@r{]} @gol -fdump-tree-ccp@r{[}-@var{n}@r{]} -fdump-tree-dce@r{[}-@var{n}@r{]} @gol -fdump-tree-gimple@r{[}-raw@r{]} -fdump-tree-mudflap@r{[}-@var{n}@r{]} @gol +-fdump-tree-tsan@r{[}-@var{n}@r{]} @gol -fdump-tree-dom@r{[}-@var{n}@r{]} @gol -fdump-tree-dse@r{[}-@var{n}@r{]} @gol -fdump-tree-phiprop@r{[}-@var{n}@r{]} @gol @@ -381,8 +382,8 @@ -floop-parallelize-all -flto -flto-compression-level @gol -flto-partition=@var{alg} -flto-report -fmerge-all-constants @gol -fmerge-constants -fmodulo-sched -fmodulo-sched-allow-regmoves @gol --fmove-loop-invariants fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol --fno-default-inline @gol +-fmove-loop-invariants -fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol +-ftsan -ftsan-ignore -fno-default-inline @gol -fno-defer-pop -fno-function-cse -fno-guess-branch-probability @gol -fno-inline -fno-math-errno -fno-peephole -fno-peephole2 @gol -fno-sched-interblock -fno-sched-spec -fno-signed-zeros @gol @@ -5896,6 +5897,11 @@ Dump each function after adding mudflap instrumentation. The file name is made by appending @file{.mudflap} to the source file name. +@item tsan +@opindex fdump-tree-tsan +Dump each function after adding ThreadSanitizer instrumentation. The file name is +made by appending @file{.tsan} to the source file name. + @item sra @opindex fdump-tree-sra Dump each function after performing scalar replacement of aggregates. The @@ -6674,6 +6680,12 @@ some protection against outright memory corrupting writes, but allows erroneously read data to propagate within a program. +@item -ftsan -ftsan-ignore +@opindex ftsan +@opindex ftsan-ignore +Add ThreadSanitizer instrumentation. Use @option{-ftsan-ignore} to specify +an ignore file. Refer to http://go/tsan for details. + @item -fthread-jumps @opindex fthread-jumps Perform optimizations where we check to see if a jump branches to a Index: gcc/tree-tsan.c =================================================================== --- gcc/tree-tsan.c (revision 0) +++ gcc/tree-tsan.c (revision 0) @@ -0,0 +1,1125 @@ +/* ThreadSanitizer instrumentation pass. + http://code.google.com/p/data-race-test + Copyright (C) 2011 + Free Software Foundation, Inc. + Contributed by Dmitry Vyukov <dvyukov@google.com> + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "intl.h" +#include "tm.h" +#include "basic-block.h" +#include "gimple.h" +#include "function.h" +#include "tree-flow.h" +#include "tree-pass.h" +#include "cfghooks.h" +#include "langhooks.h" +#include "output.h" +#include "options.h" +#include "target.h" + +/* The file can be compiled either as compiler pass or plugin. */ +#ifdef GCC_PLG +# include "c-common.h" +#else +# include "c-family/c-common.h" +#endif + +#include "diagnostic.h" + +#include <stdlib.h> +#include <stdio.h> + +/* ThreadSanitizer is a data race detector for C/C++ programs. + http://code.google.com/p/data-race-test/wiki/ThreadSanitizer + + The tool consists of two parts: + instrumentation module (this file) and a run-time library. + The instrumentation module maintains shadow call stacks + and intercepts interesting memory accesses. + The instrumentation is enabled with -ftsan flag. + + Instrumentation for shadow stack maintenance is as follows: + void somefunc () + { + __tsan_shadow_stack [-1] = __builtin_return_address (0); + __tsan_shadow_stack++; + // function body + __tsan_shadow_stack--; + } + + Interception for memory access interception is as follows: + *addr = 1; + __tsan_handle_mop (addr, flags); + where flags are (is_sblock | (is_store << 1) | ((sizeof (*addr) - 1) << 2). + is_sblock is used merely for optimization purposes and can always + be set to 1, see comments in instrument_mops function. + + Ignore files can be used to selectively non instrument some functions. + Ignore file is specified with -ftsan-ignore=filename flag. + There are 3 types of ignores: (1) do not instrument memory accesses + in the function, (2) do not create sblocks in the function + and (3) recursively ignore memory accesses in the function. + That last ignore type requires additional instrumentation of the form: + void somefunc () + { + __tsan_thread_ignore++; + // function body + __tsan_thread_ignore--; + } + + The run-time library provides __tsan_handle_mop function, + definitions of __tsan_shadow_stack and __tsan_thread_ignore variables, + and intercepts synchronization related functions. */ + +#define TSAN_IGNORE "__tsan_thread_ignore" +#define TSAN_STACK "__tsan_shadow_stack" +#define TSAN_MOP "__tsan_handle_mop" +#define TSAN_PERFIX "__tsan_" +#define MAX_MOP_BYTES 16 +#define SBLOCK_SIZE 5 + +enum tsan_ignore_type +{ + tsan_ignore_none = 1 << 0, /* Do not ignore. */ + tsan_ignore_func = 1 << 1, /* Completely ignore the whole func. */ + tsan_ignore_mop = 1 << 2, /* Do not instrument accesses. */ + tsan_ignore_rec = 1 << 3, /* Do not instrument accesses recursively. */ + tsan_ignore_hist = 1 << 4 /* Do not create superblocks. */ +}; + +/* Info associated with each basic block. + Used to determine super-blocks (see instrument_mops ()). */ + +struct bb_data +{ + int is_visited; + int has_sb; + const char *sb_file; + int sb_line_min; + int sb_line_max; +}; + +/* Memory access descriptor. */ + +struct mop_desc +{ + int is_call; + gimple_stmt_iterator gsi; + tree expr; + tree dtor_vptr_expr; + int is_store; +}; + +struct tsan_ignore_desc +{ + struct tsan_ignore_desc *next; + enum tsan_ignore_type type; + char *name; +}; + +/* Number of instrumented memory accesses in the current function. */ + +static int func_mops; + +/* Number of function calls in the current function. */ + +static int func_calls; + +/* Ignore status for the current function (see tsan_ignore_type). */ + +static enum tsan_ignore_type func_ignore; + +static int ignore_init = 0; +static struct tsan_ignore_desc *ignore_head; + +typedef struct mop_desc mop_desc; +DEF_VEC_O (mop_desc); +DEF_VEC_ALLOC_O (mop_desc, heap); +static VEC (mop_desc, heap) *mop_list; + +/* The pass references lookup_name () + that is not available in some modules (e.g. lto1). + lookup_name () is used only for testing purposes - unit tests may declare + runtime variables and functions themselves, if so we want to use those + declarations. So we just define the weak stub that is overridden + in modules that provide the function (e.g. cc1). */ + +tree __attribute__ ((weak)) +lookup_name (tree t) +{ + (void)t; + return NULL_TREE; +} + +/* Builds a definition of a runtime variable with type TYP and name NAME. */ + +static tree +build_var_decl (tree typ, const char *name) +{ + tree def; + tree id; + + /* Check if a user has defined it for testing. */ + id = get_identifier (name); + def = lookup_name (id); + if (def != NULL) + return def; + + def = build_decl (UNKNOWN_LOCATION, VAR_DECL, id, typ); + TREE_STATIC (def) = 1; + TREE_PUBLIC (def) = 1; + DECL_EXTERNAL (def) = 1; + if (targetm.have_tls) + DECL_TLS_MODEL (def) = decl_default_tls_model (def); + TREE_USED (def) = 1; + TREE_THIS_VOLATILE (def) = 1; + SET_DECL_ASSEMBLER_NAME (def, id); + return def; +} + +/* Builds the following decl + extern __thread void **__tsan_shadow_stack; */ + +static tree +get_shadow_stack_decl (void) +{ + static tree def; + + if (def == NULL) + def = build_var_decl (build_pointer_type (ptr_type_node), TSAN_STACK); + return def; +} + +/* Builds the following decl + extern __thread int __tsan_thread_ignore; */ + +static tree +get_thread_ignore_decl (void) +{ + static tree def; + + if (def == NULL) + def = build_var_decl (integer_type_node, TSAN_IGNORE); + return def; +} + +/* Builds the following decl + void __tsan_handle_mop (void *addr, unsigned flags); */ + +static tree +get_handle_mop_decl (void) +{ + tree fn_type; + + static tree def; + + if (def != NULL) + return def; + + /* Check if a user has defined it for testing. */ + + def = lookup_name (get_identifier (TSAN_MOP)); + if (def != NULL) + return def; + + fn_type = build_function_type_list (void_type_node, ptr_type_node, + integer_type_node , NULL_TREE); + def = build_fn_decl (TSAN_MOP, fn_type); + TREE_NOTHROW (def) = 1; + DECL_ATTRIBUTES (def) = tree_cons (get_identifier ("leaf"), + NULL, DECL_ATTRIBUTES (def)); + DECL_ASSEMBLER_NAME (def); + return def; +} + +/* Adds new ignore definition to the global list. + TYPE is the ignore type (see tsan_ignore_type). + NAME is the ignore pattern (e.g. "std*string*insert"). */ + +static void +ignore_append (enum tsan_ignore_type type, char *name) +{ + struct tsan_ignore_desc *desc; + + desc = XCNEW (struct tsan_ignore_desc); + desc->type = type; + desc->name = xstrdup (name); + desc->next = ignore_head; + ignore_head = desc; +} + +/* Checks as to whether identifier STR matches template TEMPL. + Templates can only contain '*', e.g. 'std*string*insert'. + Templates implicitly start and end with '*' + since they are matched against mangled names. + Returns non-zero if STR is matched against TEMPL. */ + +static int +ignore_match (char *templ, const char *str) +{ + char *tpos; + const char *spos; + + while (templ && templ [0]) + { + if (templ [0] == '*') + { + templ++; + continue; + } + if (str [0] == 0) + return 0; + tpos = strchr (templ, '*'); + if (tpos != NULL) + tpos [0] = 0; + spos = strstr (str, templ); + str = spos + strlen (templ); + templ = tpos; + if (tpos != NULL) + tpos [0] = '*'; + if (spos == NULL) + return 0; + } + return 1; +} + +/* Loads ignore definitions from the file specified by -ftsan-ignore=filename. + Ignore files have the following format: + +# This is a comment - ignored + +# The below line says to not instrument memory accesses +# in all functions that match 'std*string*insert' +fun:std*string*insert + +# The below line says to not instrument memory accesses +# in the function called 'foobar' *and* in all functions +# that it calls recursively +fun_r:foobar + +# The below line says to not create superblocks +# in the function called 'barbaz' +fun_hist:barbaz + +# Ignore all functions in the source file +src:atomic.c + +# Everything else is uninteresting for us (e.g. obj:) +*/ + +static void +ignore_load (void) +{ + FILE *f; + char *line; + size_t linesz; + ssize_t sz; + char buf [PATH_MAX]; + + if (flag_tsan_ignore == NULL || flag_tsan_ignore [0] == 0) + return; + + f = fopen (flag_tsan_ignore, "r"); + if (f == NULL) + { + /* Try to open it relative to main_input_filename. */ + strncpy (buf, main_input_filename, sizeof (buf)); + buf [sizeof (buf) - 1] = 0; + line = strrchr (buf, '/'); + if (line != NULL) + { + line++; + strncpy (line, flag_tsan_ignore, sizeof (buf) - (line - buf)); + buf [sizeof (buf) - 1] = 0; + f = fopen (buf, "r"); + } + } + if (f == NULL) + { + error ("failed to open ignore file '%s'\n", flag_tsan_ignore); + return; + } + + line = 0; + linesz = 0; + while ((sz = getline (&line, &linesz, f)) != -1) + { + if (sz == 0) + continue; + /* Strip line terminator. */ + if (line [sz - 1] == '\r' || line [sz - 1] == '\n') + line [sz - 1] = 0; + if (strncmp (line, "src:", sizeof ("src:") - 1) == 0) + ignore_append (tsan_ignore_func, line + sizeof ("src:") - 1); + else if (strncmp (line, "fun:", sizeof ("fun:") - 1) == 0) + ignore_append (tsan_ignore_mop, line + sizeof ("fun:") - 1); + else if (strncmp (line, "fun_r:", sizeof ("fun_r:") - 1) == 0) + ignore_append (tsan_ignore_rec, line + sizeof ("fun_r:") - 1); + else if (strncmp (line, "fun_hist:", sizeof ("fun_hist:") - 1) == 0) + ignore_append (tsan_ignore_hist, line + sizeof ("fun_hist:") - 1); + /* Other lines are not interesting. */ + } + + free (line); + fclose (f); +} + +/* Returns ignore status for the current function. */ + +static enum tsan_ignore_type +tsan_ignore (void) +{ + const char *func_name; + const char *src_name; + struct tsan_ignore_desc *desc; + + if (ignore_init == 0) + { + ignore_load (); + ignore_init = 1; + } + + src_name = expand_location (cfun->function_start_locus).file; + if (src_name == NULL) + src_name = ""; + + func_name = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (cfun->decl)); + /* Ignore all functions starting with __tsan_ - intended for testing. */ + if (strncmp (func_name, TSAN_PERFIX, sizeof (TSAN_PERFIX) - 1) == 0) + return tsan_ignore_func; + + for (desc = ignore_head; desc; desc = desc->next) + { + if (desc->type == tsan_ignore_func) + { + if (ignore_match (desc->name, src_name)) + return desc->type; + } + else if (ignore_match (desc->name, func_name)) + return desc->type; + } + return tsan_ignore_none; +} + +/* Forgiving variant of IDENTIFIER_POINTER (DECL_NAME (DECL)). + In case of any errors (e.g. not a decl or NULL) returns "<unknown>". */ + +static const char * +decl_name (tree decl) +{ + tree id; + const char *name; + + if (decl != 0 && DECL_P (decl)) + { + id = DECL_NAME (decl); + if (id != NULL) + { + name = IDENTIFIER_POINTER (id); + if (name != NULL) + return name; + } + } + return "<unknown>"; +} + +/* Builds either (__tsan_shadow_stack += 1) or (__tsan_shadow_stack -= 1) + expression depending on DO_DEC parameter. Appends the result to SEQ. */ + +static void +build_stack_op (gimple_seq *seq, bool do_dec) +{ + tree op_size; + double_int op_size_cst; + unsigned long long size_val; + unsigned long long size_valhi; + tree op_expr; + tree assign; + tree sstack_decl; + gimple_seq s; + + op_size = TYPE_SIZE (ptr_type_node); + op_size_cst = tree_to_double_int (op_size); + size_val = op_size_cst.low / BITS_PER_UNIT; + size_valhi = 0; + if (do_dec) + { + size_val = -size_val; + size_valhi = -1; + } + op_size = build_int_cst_wide (sizetype, size_val, size_valhi); + sstack_decl = get_shadow_stack_decl (); + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, sstack_decl, op_size); + assign = build2 (MODIFY_EXPR, ptr_type_node, sstack_decl, op_expr); + s = NULL; + force_gimple_operand (assign, &s, true, NULL_TREE); + gimple_seq_add_seq (seq, s); +} + +/* Builds either (__tsan_thread_ignore += 1) or (__tsan_thread_ignore -= 1) + expression depending on OP parameter. Stores the result in SEQ. */ + +static void +build_rec_ignore_op (gimple_seq *seq, enum tree_code op) +{ + tree rec_expr; + gimple_seq rec_inc; + gimple rec_assign; + tree ignore_decl; + + ignore_decl = get_thread_ignore_decl (); + rec_expr = build2 (op, integer_type_node, ignore_decl, integer_one_node); + rec_inc = NULL; + rec_expr = force_gimple_operand (rec_expr, &rec_inc, true, NULL_TREE); + rec_assign = gimple_build_assign (ignore_decl, rec_expr); + gimple_seq_add_seq (seq, rec_inc); + gimple_seq_add_stmt (seq, rec_assign); +} + +/* Build the following gimple sequence: + __tsan_shadow_stack [-1] = __builtin_return_address (0); + Stores the result in SEQ. */ + +static void +build_stack_assign (gimple_seq *seq) +{ + tree pc_addr; + tree op_size; + tree op_expr; + tree stack_op; + tree assign; + tree retaddr_decl; + + retaddr_decl = implicit_built_in_decls [BUILT_IN_RETURN_ADDRESS]; + pc_addr = build_call_expr (retaddr_decl, 1, integer_zero_node); + op_size = build_int_cst_wide (sizetype, -(POINTER_SIZE / BITS_PER_UNIT), -1); + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, + get_shadow_stack_decl (), op_size); + stack_op = build1 (INDIRECT_REF, ptr_type_node, op_expr); + assign = build2 (MODIFY_EXPR, ptr_type_node, stack_op, pc_addr); + force_gimple_operand (assign, seq, true, NULL_TREE); +} + +/* Builds the following gimple sequence: + __tsan_handle_mop (&EXPR, + (IS_SBLOCK | (IS_STORE << 1) | ((sizeof (EXPR) - 1) << 2); + The result is stored in GSEQ. */ + +static void +instr_mop (tree expr, int is_store, int is_sblock, gimple_seq *gseq) +{ + tree addr_expr; + tree expr_type; + unsigned size; + unsigned flags; + tree flags_expr; + tree call_expr; + + gcc_assert (gseq != 0 && *gseq == 0); + gcc_assert (is_gimple_addressable (expr)); + + addr_expr = build_addr (expr, current_function_decl); + expr_type = TREE_TYPE (expr); + while (TREE_CODE (expr_type) == ARRAY_TYPE) + expr_type = TREE_TYPE (expr_type); + size = TREE_INT_CST_LOW (TYPE_SIZE (expr_type)); + size = size / BITS_PER_UNIT; + if (size > MAX_MOP_BYTES) + size = MAX_MOP_BYTES; + size -= 1; + flags = ((!!is_sblock << 0) + (!!is_store << 1) + (size << 2)); + flags_expr = build_int_cst (unsigned_type_node, flags); + call_expr = build_call_expr (get_handle_mop_decl (), + 2, addr_expr, flags_expr); + force_gimple_operand (call_expr, gseq, true, 0); +} + +/* Builds the following gimple sequence: + int is_store = (EXPR != RHS); // The temp is not actually introduced. + __tsan_handle_mop (&EXPR, + (IS_SBLOCK | (IS_STORE << 1) | ((sizeof (EXPR) - 1) << 2); + The result is stored in GSEQ. */ + +static void +instr_vptr_store (tree expr, tree rhs, int is_sblock, gimple_seq *gseq) +{ + tree expr_ptr; + tree addr_expr; + tree expr_type; + tree expr_size; + double_int size; + unsigned flags; + tree flags_expr; + gimple_seq flags_seq; + gimple collect; + tree is_store_expr; + + expr_ptr = build_addr (expr, current_function_decl); + addr_expr = force_gimple_operand (expr_ptr, gseq, true, NULL_TREE); + expr_type = TREE_TYPE (expr); + while (TREE_CODE (expr_type) == ARRAY_TYPE) + expr_type = TREE_TYPE (expr_type); + expr_size = TYPE_SIZE (expr_type); + size = tree_to_double_int (expr_size); + gcc_assert (size.high == 0 && size.low != 0); + if (size.low > 128) + size.low = 128; + size.low = (size.low / 8) - 1; + flags = ((!!is_sblock << 0) + (size.low << 2)); + flags_expr = build_int_cst (unsigned_type_node, flags); + is_store_expr = build2 (NE_EXPR, unsigned_type_node, + build1 (VIEW_CONVERT_EXPR, size_type_node, expr), + build1 (VIEW_CONVERT_EXPR, size_type_node, rhs)); + is_store_expr = build2 (LSHIFT_EXPR, unsigned_type_node, + is_store_expr, integer_one_node); + flags_expr = build2 (BIT_IOR_EXPR, unsigned_type_node, + is_store_expr, flags_expr); + flags_seq = 0; + flags_expr = force_gimple_operand (flags_expr, &flags_seq, true, NULL_TREE); + gimple_seq_add_seq (gseq, flags_seq); + collect = gimple_build_call ( + get_handle_mop_decl (), 2, addr_expr, flags_expr); + gimple_seq_add_stmt (gseq, collect); +} + +/* Builds gimple sequences that must be inserted at function entry (PRE) + and before function exit (POST). */ + +static void +instr_func (gimple_seq *pre, gimple_seq *post) +{ + /* In this case we need no instrumentation for the function. */ + if (func_calls == 0 && func_mops == 0) + return; + + if (func_ignore != tsan_ignore_rec) + { + build_stack_assign (pre); + build_stack_op (pre, false); + build_stack_op (post, true); + } + + if (func_ignore == tsan_ignore_rec && func_calls != 0) + { + build_rec_ignore_op (pre, PLUS_EXPR); + build_rec_ignore_op (post, MINUS_EXPR); + } +} + +/* Sets location LOC for all gimples in the SEQ. */ + +static void +set_location (gimple_seq seq, location_t loc) +{ + gimple_seq_node n; + + for (n = gimple_seq_first (seq); n != NULL; n = n->next) + gimple_set_location (n->stmt, loc); +} + +/* Check as to whether EXPR refers to a store to vptr. */ + +static tree +is_dtor_vptr_store (gimple stmt, tree expr, int is_store) +{ + if (is_store == 1 + && TREE_CODE (expr) == COMPONENT_REF + && gimple_assign_single_p (stmt) + && strcmp (decl_name (cfun->decl), "__base_dtor ") == 0) + { + tree comp = TREE_OPERAND (expr, 0); + while (TREE_CODE (comp) == COMPONENT_REF) + comp = TREE_OPERAND (comp, 0); + if (TREE_CODE (comp) == INDIRECT_REF || TREE_CODE (comp) == MEM_REF) + { + comp = TREE_OPERAND (comp, 0); + if (TREE_CODE (comp) == SSA_NAME) + comp = SSA_NAME_VAR (comp); + if (strcmp (decl_name (comp), "this") == 0) + { + tree field = TREE_OPERAND (expr, 1); + if (TREE_CODE (field) == FIELD_DECL + && strncmp (decl_name (field), + "_vptr.", sizeof ("_vptr.") - 1) == 0) + return gimple_assign_rhs1 (stmt); + } + } + } + return 0; +} + +/* Checks as to whether EXPR refers to a read from vtlb. + Vtlbs are immutable, so don't bother to instrument them. */ + +static int +is_vtbl_read (tree expr, int is_store) +{ + /* We may not instrument reads from vtbl, because the data is constant. + vtbl read is of the form: + gimple_assign <component_ref, D.2133, x->_vptr.X, NULL> + gimple_assign <indirect_ref, D.2134, *D.2133, NULL> + or: + gimple_assign <component_ref, D.2133, x->_vptr.X, NULL> + gimple_assign <pointer_plus_expr, D.2135, D.2133, 8> + gimple_assign <indirect_ref, D.2136, *D.2135, NULL> */ + + if (is_store == 0 + && TREE_CODE (expr) == INDIRECT_REF) + { + tree ref_target = TREE_OPERAND (expr, 0); + if (TREE_CODE (ref_target) == SSA_NAME) + { + gimple ref_stmt = ref_target->ssa_name.def_stmt; + if (gimple_code (ref_stmt) == GIMPLE_ASSIGN) + { + if (gimple_expr_code (ref_stmt) == POINTER_PLUS_EXPR) + { + tree tmp = ref_stmt->gsmem.op [1]; + if (TREE_CODE (tmp) == SSA_NAME + && gimple_code (tmp->ssa_name.def_stmt) == GIMPLE_ASSIGN) + ref_stmt = tmp->ssa_name.def_stmt; + } + if (gimple_expr_code (ref_stmt) == COMPONENT_REF + && gimple_assign_single_p (ref_stmt)) + { + tree comp_expr = ref_stmt->gsmem.op [1]; + tree field_expr = TREE_OPERAND (comp_expr, 1); + if (TREE_CODE (field_expr) == FIELD_DECL + && strncmp (decl_name (field_expr), + "_vptr.", sizeof ("_vptr.") - 1) == 0) + return 1; + } + } + } + } + + return 0; +} + +/* Checks as to whether EXPR refers to constant var/field/param. + Don't bother to instrument them. */ + +static int +is_load_of_const (tree expr, int is_store) +{ + if (is_store == 0) + { + if (TREE_CODE (expr) == COMPONENT_REF) + expr = TREE_OPERAND (expr, 1); + if (TREE_CODE (expr) == VAR_DECL + || TREE_CODE (expr) == PARM_DECL + || TREE_CODE (expr) == FIELD_DECL) + { + if (TREE_READONLY (expr)) + return 1; + } + } + return 0; +} + +/* Checks as to whether EXPR needs to be instrumented, + if so puts it into the MOP_LIST. + STMT is the gimple from which EXPR was extracted. + GSI is the iterator from which STMT was extracted. + IS_STORE says as to whether EXPR refers to a memory store + or a memory load. */ + +static void +handle_expr (gimple stmt, gimple_stmt_iterator gsi, + tree expr, int is_store, VEC (mop_desc, heap) **mop_list) +{ + enum tree_code tcode; + struct mop_desc mop; + unsigned fld_off; + unsigned fld_size; + + tcode = TREE_CODE (expr); + + /* Below are things we do not instrument + (no possibility of races or not implemented yet). */ + if ((func_ignore & (tsan_ignore_mop | tsan_ignore_rec)) + || tcode == SSA_NAME + /* Store to function result. */ + || tcode == RESULT_DECL + /* Various constant literals. */ + || TREE_CODE_CLASS (tcode) == tcc_constant + /* Compiler-emitted artificial variables. */ + || (TREE_CODE_CLASS (tcode) == tcc_declaration && DECL_ARTIFICIAL (expr)) + /* The var does not live in memory -> no possibility of races. */ + || (tcode == VAR_DECL + && TREE_ADDRESSABLE (expr) == 0 + && DECL_EXTERNAL (expr) == 0) + /* TODO (dvyukov): not implemented. */ + || TREE_CODE (TREE_TYPE (expr)) == RECORD_TYPE + /* TODO (dvyukov): not implemented. */ + || tcode == CONSTRUCTOR + /* TODO (dvyukov): not implemented. */ + || tcode == PARM_DECL + /* Load of a const variable/parameter/field. */ + || is_load_of_const (expr, is_store) + /* Vtbl read. */ + || is_vtbl_read (expr, is_store)) + return; + + if (tcode == COMPONENT_REF) + { + tree field = TREE_OPERAND (expr, 1); + if (TREE_CODE (field) == FIELD_DECL) + { + fld_off = TREE_INT_CST_LOW (DECL_FIELD_BIT_OFFSET (field)); + fld_size = TREE_INT_CST_LOW (DECL_SIZE (field)); + if (((fld_off % BITS_PER_UNIT) != 0) + || ((fld_size % BITS_PER_UNIT) != 0)) + { + /* As of now it crashes compilation. + TODO (dvyukov): handle bit-fields as if touching + the whole field. */ + return; + } + } + } + + /* TODO (dvyukov): handle other cases + (FIELD_DECL, MEM_REF, ARRAY_RANGE_REF, TARGET_MEM_REF, ADDR_EXPR). */ + if (tcode != ARRAY_REF + && tcode != VAR_DECL + && tcode != COMPONENT_REF + && tcode != INDIRECT_REF + && tcode != MEM_REF) + return; + + mop.is_call = 0; + mop.gsi = gsi; + mop.expr = expr; + mop.dtor_vptr_expr = is_dtor_vptr_store (stmt, expr, is_store); + mop.is_store = is_store; + VEC_safe_push (mop_desc, heap, *mop_list, &mop); +} + +/* Collects all interesting memory accesses from the gimple pointed to by GSI + into MOP_LIST. */ + +static void +handle_gimple (gimple_stmt_iterator gsi, VEC (mop_desc, heap) **mop_list) +{ + unsigned i; + struct mop_desc mop; + gimple stmt; + enum gimple_code gcode; + tree rhs; + tree lhs; + + stmt = gsi_stmt (gsi); + gcode = gimple_code (stmt); + if (gcode >= LAST_AND_UNUSED_GIMPLE_CODE) + return; + + switch (gcode) + { + /* TODO (dvyukov): handle GIMPLE_COND (can it access memmory?). */ + case GIMPLE_CALL: + { + func_calls += 1; + memset (&mop, 0, sizeof (mop)); + mop.is_call = 1; + VEC_safe_push (mop_desc, heap, *mop_list, &mop); + break; + } + + case GIMPLE_ASSIGN: + { + /* Handle assignment lhs as store. */ + lhs = gimple_assign_lhs (stmt); + handle_expr (stmt, gsi, lhs, 1, mop_list); + + /* Handle operands as loads. */ + for (i = 1; i < gimple_num_ops (stmt); i++) + { + rhs = gimple_op (stmt, i); + handle_expr (stmt, gsi, rhs, 0, mop_list); + } + break; + } + + default: + break; + } +} + +/* Instruments single basic block BB. */ + +static void +instrument_bblock (struct bb_data *bbd, basic_block bb) +{ + int ix; + int is_sblock; + gimple_stmt_iterator gsi; + struct mop_desc *mop; + gimple stmt; + location_t loc; + expanded_location eloc; + gimple_seq instr_seq; + + /* Iterate over all gimples and collect interesting mops into mop_list. */ + VEC_free (mop_desc, heap, mop_list); + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + handle_gimple (gsi, &mop_list); + } + + mop = 0; + for (ix = 0; VEC_iterate (mop_desc, mop_list, ix, mop); ix += 1) + { + if (mop->is_call != 0) + { + /* After a function call we must start a brand new sblock, + because the function can contain synchronization. */ + bbd->has_sb = 0; + continue; + } + + func_mops += 1; + stmt = gsi_stmt (mop->gsi); + loc = gimple_location (stmt); + eloc = expand_location (loc); + + /* Check as to whether we may not set sblock flag for the access. */ + is_sblock = (bbd->has_sb == 0 + || !(eloc.file != 0 + && bbd->sb_file != 0 + && strcmp (eloc.file, bbd->sb_file) == 0 + && eloc.line >= bbd->sb_line_min + && eloc.line <= bbd->sb_line_max)); + + if (func_ignore == tsan_ignore_hist) + is_sblock = 0; + + if (is_sblock) + { + /* Start new sblock with new source info. */ + bbd->has_sb = 1; + bbd->sb_file = eloc.file; + bbd->sb_line_min = eloc.line; + bbd->sb_line_max = eloc.line + SBLOCK_SIZE; + } + + instr_seq = 0; + if (mop->dtor_vptr_expr == 0) + instr_mop (mop->expr, mop->is_store, is_sblock, &instr_seq); + else + instr_vptr_store (mop->expr, mop->dtor_vptr_expr, + is_sblock, &instr_seq); + gcc_assert (instr_seq != 0); + set_location (instr_seq, loc); + /* Instrumentation for assignment of a function result + must be inserted after the call. Instrumentation for + reads of function arguments must be inserted before the call. + That's because the call can contain synchronization. */ + if (is_gimple_call (stmt) && mop->is_store == 1) + gsi_insert_seq_after (&mop->gsi, instr_seq, GSI_NEW_STMT); + else + gsi_insert_seq_before (&mop->gsi, instr_seq, GSI_SAME_STMT); + } +} + +/* Instruments all interesting memory accesses in the function. */ + +static void +instrument_mops (void) +{ + basic_block bb; + int *blocks_inverted; + struct bb_data *bb_data; + struct bb_data *pred; + struct bb_data *bbd; + edge e; + int sb_line_min, sb_line_max; + int cnt, eidx, i; + + /* The function does basic block traversal in reverse top sort order + of the inverted CFG. Such order is required to properly mark super-blocks. + The idea behind super-blocks is as follows. + If several memory accesses happen within SBLOCK_SIZE source code lines + from each other, then we only mark the first access as SBLOCK. + This allows the runtime library to memorize a stack trace + only for the first access and do not memorize for others. + This significantly reduces memory consumption in exchange for slightly + imprecise stack traces for previous accesses. */ + + blocks_inverted = XNEWVEC (int, last_basic_block + NUM_FIXED_BLOCKS); + bb_data = XCNEWVEC (struct bb_data, last_basic_block + NUM_FIXED_BLOCKS); + cnt = inverted_post_order_compute (blocks_inverted); + for (i = 0; i < cnt; i++) + { + bb = BASIC_BLOCK (blocks_inverted [i]); + bbd = &bb_data [bb->index]; + /* Iterate over all predecessors and merge their sblock info. */ + e = 0; + for (eidx = 0; VEC_iterate (edge, bb->preds, eidx, e); eidx++) + { + pred = &bb_data [e->src->index]; + if (!pred->is_visited || !pred->has_sb || pred == bbd) + { + /* If there is a not visited predecessor, + or a predecessor with no active sblock info, + or a self-loop, then we will have to start + a brand new sblock on next memory access. */ + bbd->has_sb = 0; + break; + } + else if (bbd->has_sb == 0) + { + /* If it's a first predecessor, just copy the info. */ + bbd->has_sb = 1; + bbd->sb_file = pred->sb_file; + bbd->sb_line_min = pred->sb_line_min; + bbd->sb_line_max = pred->sb_line_max; + } + else + { + /* Otherwise, find the interception + between two sblock descriptors. */ + bbd->has_sb = 0; + if (bbd->sb_file != 0 && pred->sb_file != 0 + && strcmp (bbd->sb_file, pred->sb_file) == 0) + { + sb_line_min = MAX (bbd->sb_line_min, pred->sb_line_min); + sb_line_max = MIN (bbd->sb_line_max, pred->sb_line_max); + if (sb_line_min <= sb_line_max) + { + bbd->has_sb = 1; + bbd->sb_line_min = sb_line_min; + bbd->sb_line_max = sb_line_max; + } + } + /* No interception, have to start new sblock. */ + if (bbd->has_sb == 0) + break; + } + } + + instrument_bblock (bbd, bb); + bbd->is_visited = 1; + } + + free (blocks_inverted); + free (bb_data); +} + +/* Instruments function entry and exit, if necessary. */ + +static void +instrument_function (void) +{ + location_t loc; + gimple_seq pre_func_seq; + gimple_seq post_func_seq; + basic_block entry_bb; + basic_block first_bb; + basic_block exit_bb; + edge entry_edge; + gimple_stmt_iterator first_gsi; + gimple_stmt_iterator gsi; + gimple first_stmt; + gimple stmt; + edge e; + int eidx; + + pre_func_seq = 0; + post_func_seq = 0; + instr_func (&pre_func_seq, &post_func_seq); + + if (pre_func_seq != 0) + { + /* Insert new BB before the first BB. */ + entry_bb = ENTRY_BLOCK_PTR; + entry_edge = single_succ_edge (entry_bb); + first_bb = entry_edge->dest; + first_gsi = gsi_start_bb (first_bb); + if (!gsi_end_p (first_gsi)) + { + first_stmt = gsi_stmt (first_gsi); + loc = gimple_location (first_stmt); + set_location (pre_func_seq, loc); + } + entry_bb = split_edge (entry_edge); + gsi = gsi_start_bb (entry_bb); + gsi_insert_seq_after (&gsi, pre_func_seq, GSI_NEW_STMT); + } + + if (post_func_seq != 0) + { + /* Find all function exits. */ + exit_bb = EXIT_BLOCK_PTR; + e = 0; + for (eidx = 0; VEC_iterate (edge, exit_bb->preds, eidx, e); eidx++) + { + gsi = gsi_last_bb (e->src); + stmt = gsi_stmt (gsi); + gcc_assert (gimple_code (stmt) == GIMPLE_RETURN); + loc = gimple_location (stmt); + set_location (post_func_seq, loc); + gsi_insert_seq_before (&gsi, post_func_seq, GSI_SAME_STMT); + } + } +} + +static unsigned +tsan_pass (void) +{ + func_ignore = tsan_ignore (); + if (func_ignore == tsan_ignore_func) + return 0; + + func_calls = 0; + func_mops = 0; + + instrument_mops (); + instrument_function (); + + return 0; +} + +static bool +tsan_gate (void) +{ + return flag_tsan != 0; +} + +struct gimple_opt_pass pass_tsan = {{ + GIMPLE_PASS, + "tsan", /* name */ + tsan_gate, /* gate */ + tsan_pass, /* execute */ + NULL, /* sub */ + NULL, /* next */ + 0, /* static_pass_number */ + TV_NONE, /* tv_id */ + PROP_trees | PROP_cfg, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + TODO_dump_cgraph | TODO_dump_func | TODO_verify_all + | TODO_update_ssa | TODO_update_address_taken /* todo_flags_finish */ +}}; + Property changes on: gcc/tree-tsan.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/tree-pass.h =================================================================== --- gcc/tree-pass.h (revision 180522) +++ gcc/tree-pass.h (working copy) @@ -352,6 +352,7 @@ extern struct gimple_opt_pass pass_mudflap_1; extern struct gimple_opt_pass pass_mudflap_2; +extern struct gimple_opt_pass pass_tsan; extern struct gimple_opt_pass pass_lower_cf; extern struct gimple_opt_pass pass_refactor_eh; extern struct gimple_opt_pass pass_lower_eh; Index: gcc/testsuite/gcc.dg/tsan-ignore.ignore =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) @@ -0,0 +1,7 @@ +#comment +fun:foo +fun:*bar +fun:baz* +fun:*bla* +fun:x*x +src:tsan-ignore.h Index: gcc/testsuite/gcc.dg/tsan.h =================================================================== --- gcc/testsuite/gcc.dg/tsan.h (revision 0) +++ gcc/testsuite/gcc.dg/tsan.h (revision 0) @@ -0,0 +1,87 @@ +/* Helper declarations and functions for ThreadSanitizer instrumentation (-ftsan) testing */ + +int printf (char *str, ...); +void exit (int); + +/* Variables referenced by the instrumentation */ +__thread void **__tsan_shadow_stack; +__thread int __tsan_thread_ignore; + +/* Local helper vars */ +__thread void *shadow_stack[1024]; +__thread int mop_expect; +__thread int mop_depth; +__thread void* mop_addr; +__thread unsigned long long mop_pc; +__thread unsigned mop_flags; + +/* Setups shadow stack var (not instrumented) */ +void __attribute__ ((constructor)) +__tsan_init (void) +{ + __tsan_shadow_stack = shadow_stack; +} + +/* Declare that we expect an instrumented memory access (not instrumented). + depth - stack depth of the mop (0 - main, 1 - func called from main and so on). + addr - memory access address. + is_store - store/load. + is_sblock - superblock flag of the access. + size - memory access size in bytes. */ +void +__tsan_expect_mop (int depth, void *addr, int is_store, int is_sblock, int size) +{ + if (mop_expect) + { + printf ("missed mop: addr=%p pc=%d\n", mop_addr, mop_pc); + exit (1); + } + + mop_expect = 1; + mop_depth = depth; + mop_addr = addr; + mop_pc = (unsigned long long)__builtin_return_address(0); + mop_flags = !!is_sblock | (!!is_store << 1) | ((size - 1) << 2); +} + +/* Memory access function (referenced by instrumentation, not instrumented). */ +void +__tsan_handle_mop (void *addr, unsigned flags) +{ + unsigned long long pc; + int depth; + + printf ("mop: addr=%p flags=%x called from %p/%p\n", addr, flags, __builtin_return_address(1), __tsan_shadow_stack[-2]); + if (mop_expect == 0) + return; + + /* Verify parameters with what we expect. */ + + if (addr != mop_addr) + { + printf ("incorrect mop addr: %p/%p\n", addr, mop_addr); + exit (1); + } + + pc = (unsigned long long)__builtin_return_address(0); + if (pc < mop_pc - 100 || pc > mop_pc + 100) + { + printf ("incorrect mop pc: %p/%p\n", (void*)pc, (void*)mop_pc); + exit (1); + } + + depth = __tsan_shadow_stack - shadow_stack - 1; + if (depth != mop_depth) + { + printf ("incorrect mop depth: %d/%d\n", depth, mop_depth); + exit (1); + } + + if (flags != mop_flags) + { + printf ("incorrect mop flags: %x/%x\n", flags, mop_flags); + exit (1); + } + + mop_expect = 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan.h ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-ignore.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) @@ -0,0 +1,49 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan -ftsan-ignore=tsan-ignore.ignore" } */ +#include "tsan.h" +#include "tsan-ignore.h" + +/* Check ignore file handling. */ + +int +foo (int *p) +{ + p [0] = 1; +} + +int bar (int *p) +{ + p [0] = 1; +} + +int baz (int *p) +{ + p [0] = 1; +} + +int bla (int *p) +{ + p [0] = 1; +} + +int xxx (int *p) +{ + p [0] = 1; +} + +int +main (void) +{ + int p, x; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p)); + /* All these functions must be ignored. */ + foo (&x); + bar (&x); + baz (&x); + bla (&x); + xxx (&x); + in_tsan_ignore_header (&x); + p = 0; + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-ignore.h =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) @@ -0,0 +1,5 @@ +int +in_tsan_ignore_header (int *p) +{ + p [0] = 1; +} Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.h ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-stack.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) @@ -0,0 +1,23 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan" } */ +#include "tsan.h" + +/* Check shadow stack maintance. */ + +int +foobar (int *p) +{ + __tsan_expect_mop(1, p, 1, 1, sizeof(*p)); + p[0] = 1; +} + +int +main (void) +{ + int p; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p)); + p = 0; + foobar (&p); + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-stack.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-mop.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) @@ -0,0 +1,29 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan" } */ +#include "tsan.h" + +/* Sanity check for memory accesses instrumentation. */ + +int +foobar (int *p) +{ + __tsan_expect_mop(1, p, 1, 1, sizeof(*p)); + p[0] = 1; + + __tsan_expect_mop(1, p, 1, 1, sizeof(*p)); + *p = 2; + + __tsan_expect_mop(1, (char*)p+3, 1, 1, 1); + *((char*)p+3) = 3; +} + +int +main (void) +{ + int p; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p)); + p = 0; + foobar (&p); + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-mop.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/common.opt =================================================================== --- gcc/common.opt (revision 180522) +++ gcc/common.opt (working copy) @@ -1547,6 +1547,14 @@ Common RejectNegative Report Var(flag_mudflap_ignore_reads) Ignore read operations when inserting mudflap instrumentation +ftsan +Common RejectNegative Report Var(flag_tsan) +Add ThreadSanitizer instrumentation + +ftsan-ignore= +Common RejectNegative Joined Var(flag_tsan_ignore) +-ftsan-ignore=filename ThreadSanitizer ignore file + fdce Common Var(flag_dce) Init(1) Optimization Use the RTL dead code elimination pass Index: gcc/Makefile.in =================================================================== --- gcc/Makefile.in (revision 180522) +++ gcc/Makefile.in (working copy) @@ -1494,6 +1494,7 @@ tree-streamer-out.o \ tree-tailcall.o \ tree-threadsafe-analyze.o \ + tree-tsan.o \ tree-vect-generic.o \ tree-vect-patterns.o \ tree-vect-data-refs.o \ @@ -2814,6 +2815,12 @@ $(C_TREE_H) $(C_COMMON_H) $(GIMPLE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) \ output.h langhooks.h tree-mudflap.h $(TM_H) coretypes.h \ $(GGC_H) gt-tree-mudflap.h $(TREE_PASS_H) $(DIAGNOSTIC_CORE_H) +tree-tsan.o : $(CONFIG_H) $(SYSTEM_H) $(TREE_H) $(TREE_INLINE_H) \ + $(GIMPLE_H) $(DIAGNOSTIC_H) langhooks.h \ + $(TM_H) coretypes.h $(TREE_DUMP_H) $(TREE_PASS_H) $(CGRAPH_H) $(GGC_H) \ + $(BASIC_BLOCK_H) $(FLAGS_H) $(FUNCTION_H) \ + $(TM_P_H) $(TREE_FLOW_H) $(DIAGNOSTIC_CORE_H) $(GIMPLE_H) tree-iterator.h \ + intl.h cfghooks.h output.h options.h c-family/c-common.h tree-pretty-print.o : tree-pretty-print.c $(CONFIG_H) $(SYSTEM_H) \ $(TREE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) $(TREE_FLOW_H) \ $(TM_H) coretypes.h tree-iterator.h $(SCEV_H) langhooks.h \ Index: gcc/passes.c =================================================================== --- gcc/passes.c (revision 180522) +++ gcc/passes.c (working copy) @@ -1344,6 +1344,7 @@ NEXT_PASS (pass_split_crit_edges); NEXT_PASS (pass_pre); NEXT_PASS (pass_sink_code); + NEXT_PASS (pass_tsan); NEXT_PASS (pass_tree_loop); { struct opt_pass **p = &pass_tree_loop.pass.sub; -- This patch is available for review at http://codereview.appspot.com/5303083
Sign in to reply to this message.
On Mon, Oct 31, 2011 at 10:08 AM, <davidxl@google.com> wrote: > Have not done with reviewing. This is the first batch. > > David > <http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode161> Hi David, Thanks! I've removed comments that are just "Done". Below are comments that I am not sure how to fix. > http://codereview.appspot.com/**5303083/diff/1/gcc/tree-tsan.** > c#newcode161<http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode161> gcc/tree-tsan.c:161: tree __attribute__((weak)) > Explain this. > Done. Is it OK to leave __attribute__((weak))? It does not look like a good solution, however it works and I am not sure how to fix it w/o the attribute. Do I actually need lookup_name ()? Normally the runtime variables and functions (e.g. __tsan_shadow_stack) are not declared in user program, however unit tests do declare them and then I just lookup them instead of building declarations manually. Is it OK to just build a second declaration w/o looking it up first? I guess normally there are no doubled declarations in the compiler. > > http://codereview.appspot.com/**5303083/diff/1/gcc/tree-tsan.** > c#newcode182<http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode182> > gcc/tree-tsan.c:182: > Better use varpool_get_node interface. > > http://codereview.appspot.com/**5303083/diff/1/gcc/tree-tsan.** > c#newcode186<http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode186> > gcc/tree-tsan.c:186: TREE_STATIC (def) = 1; > Why mark TREE_STATIC (def) = 1? Should the variable be defined in tsan > library? > How to properly use varpool in this case? Initially I build them as: def = build_decl (UNKNOWN_LOCATION, VAR_DECL, get_identifier (RTL_STACK), build_pointer_type (ptr_type_node)); TREE_STATIC (def) = 1; TREE_PUBLIC (def) = 0; DECL_INITIAL (def) = NULL; DECL_TLS_MODEL (def) = decl_default_tls_model (def); DECL_ARTIFICIAL (def) = 1; varpool_finalize_decl (def); varpool_mark_needed_node (varpool_node (def)); Taken from tree-profile.c by your suggestion. However it does not seem to work for me - programs crash. Current code is taken from elsewhere and seem to work for me. What I need to build is: extern __thread int __tsan_thread_ignore; */ Yes, they are defined in tsan library - I just need declarations. > http://codereview.appspot.com/**5303083/diff/1/gcc/tree-tsan.** > c#newcode291<http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode291> > gcc/tree-tsan.c:291: if (spos == NULL) > Move the check up right after spos is computed. > It won't work. Template restoration (tpos [0] = '*') must precede the check (if (spos == NULL)), while length calculation (strlen (templ)) must precede template restoration. > > http://codereview.appspot.com/**5303083/diff/1/gcc/tree-tsan.** > c#newcode413<http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode413> > gcc/tree-tsan.c:413: static const char * > Missing documentation. > Done. Do I need to document all function and function parameters (including trivial)? > > http://codereview.appspot.com/**5303083/diff/1/gcc/tree-tsan.** > c#newcode459<http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode459> > gcc/tree-tsan.c:459: s = NULL; > MODIFY_EXPR? directly use gimple_build_assign. > I've figured out that a single tree can produce several gimples due to limitations on gimples (e.g. a function call may have argument calculations as separate gimples). So I've concluded that the safe way to do it is to build a full tree first and then convert it to gimple seq. Isn't it the right way? What is the idiomatic way to build statements? Here are some additional concerns: Please take a look at pass.properties_required and .todo_flags_finish. I am not sure what needs to be there. After experiencing a lot of compiler crashes I incrementally pull there basically everything (update and verify everything just in case...) Can GIMPLE_COND contain memory accesses? Since GIMPLE_CALL can't I suspect that GIMPLE_COND can't as well. There is some clumsy code in is_vtbl_read () and is_dtor_vptr_store (), is there a better to way to detect accesses to vptr and vtbl?
Sign in to reply to this message.
http://codereview.appspot.com/5303083/diff/3002/gcc/tree-tsan.c File gcc/tree-tsan.c (right): http://codereview.appspot.com/5303083/diff/3002/gcc/tree-tsan.c#newcode1082 gcc/tree-tsan.c:1082: gsi_insert_seq_before (&gsi, post_func_seq, GSI_SAME_STMT); Do I need to make a copy of POST_FUNC_SEQ here? I think that I do not need location info for this code at all, so is it OK to leave the seq w/o location and then insert it into several basic blocks?
Sign in to reply to this message.
On 2011/10/31 06:08:34, davidxl wrote: > http://codereview.appspot.com/5303083/diff/1/gcc/passes.c#newcode1423 > gcc/passes.c:1423: NEXT_PASS (pass_tsan); > Move this to the same place as asan. Otherwise TARGET_MEM_REF won't be handled. After I moved the pass it started crashing: Program received signal SIGSEGV, Segmentation fault. 0x0000000000718f94 in is_gimple_reg_type (t=0x7ffff771efa0) at gimple.c:2960 2960 return !AGGREGATE_TYPE_P (type); (gdb) bt #0 0x0000000000718f94 in is_gimple_reg_type (t=0x7ffff771efa0) at gimple.c:2960 #1 is_gimple_val (t=0x7ffff771efa0) at gimple.c:3028 #2 0x00000000008a5d20 in verify_types_in_gimple_reference (expr=0x7ffff76b74c0, require_lvalue=false) at tree-cfg.c:2934 #3 0x00000000008b2d4f in verify_gimple_in_cfg (fn=0x7ffff77c67e0) at tree-cfg.c:4382 #4 0x0000000000a061d6 in verify_ssa (check_modified_stmt=true) at tree-ssa.c:924 #5 0x00000000007f755c in execute_function_todo (data=Unhandled dwarf expression opcode 0xf3 ) at passes.c:1727 #6 0x00000000007f7e4d in execute_todo (flags=34854) at passes.c:1758 #7 0x00000000007fafda in execute_one_pass (pass=0x122a900) at passes.c:2104 The code seems to be (however I am not 100% sure): D.3617_33 = MEM[(const uint64_t *)ctx_11(D)].nhkey[D.3612_26]{lb: D.3810_279 sz: 8};
Sign in to reply to this message.
On Mon, Oct 31, 2011 at 5:54 AM, Dmitry Vyukov <dvyukov@google.com> wrote: > On Mon, Oct 31, 2011 at 10:08 AM, <davidxl@google.com> wrote: > >> Have not done with reviewing. This is the first batch. >> >> David >> <http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode161> > > > Hi David, > > Thanks! > I've removed comments that are just "Done". > Below are comments that I am not sure how to fix. > > > >> http://codereview.appspot.com/**5303083/diff/1/gcc/tree-tsan.** >> c#newcode161<http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode161> > > gcc/tree-tsan.c:161: tree __attribute__((weak)) >> Explain this. >> > > Done. > Is it OK to leave __attribute__((weak))? It does not look like a good > solution, however it works and I am not sure how to fix it w/o the > attribute. > Do I actually need lookup_name ()? Normally the runtime variables and > functions (e.g. __tsan_shadow_stack) are not declared in user program, > however unit tests do declare them and then I just lookup them instead of > building declarations manually. Is it OK to just build a second declaration > w/o looking it up first? I guess normally there are no doubled declarations > in the compiler. > You don't need this function. I mentioned you can use varpool_node_for_asm method to do the variable lookup in middle end. > > > > >> >> http://codereview.appspot.com/**5303083/diff/1/gcc/tree-tsan.** >> c#newcode182<http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode182> >> gcc/tree-tsan.c:182: >> Better use varpool_get_node interface. >> >> http://codereview.appspot.com/**5303083/diff/1/gcc/tree-tsan.** >> c#newcode186<http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode186> >> gcc/tree-tsan.c:186: TREE_STATIC (def) = 1; >> Why mark TREE_STATIC (def) = 1? Should the variable be defined in tsan >> library? >> > > How to properly use varpool in this case? > Initially I build them as: > > def = build_decl (UNKNOWN_LOCATION, VAR_DECL, > get_identifier (RTL_STACK), > build_pointer_type (ptr_type_node)); > TREE_STATIC (def) = 1; > TREE_PUBLIC (def) = 0; > DECL_INITIAL (def) = NULL; > DECL_TLS_MODEL (def) = decl_default_tls_model (def); > DECL_ARTIFICIAL (def) = 1; > varpool_finalize_decl (def); > varpool_mark_needed_node (varpool_node (def)); > > Taken from tree-profile.c by your suggestion. However it does not seem to > work for me - programs crash. > Current code is taken from elsewhere and seem to work for me. > What I need to build is: > > extern __thread int __tsan_thread_ignore; */ > > Yes, they are defined in tsan library - I just need declarations. > > I meant varpool_node_for_asm method. This method's implementation is not efficient, but it should be find as you only do this once per file. > > > >> http://codereview.appspot.com/**5303083/diff/1/gcc/tree-tsan.** >> c#newcode291<http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode291> >> gcc/tree-tsan.c:291: if (spos == NULL) >> Move the check up right after spos is computed. >> > > It won't work. Template restoration (tpos [0] = '*') must precede the > check (if (spos == NULL)), while length calculation (strlen (templ)) must > precede template restoration. > > Ok. > > > >> >> http://codereview.appspot.com/**5303083/diff/1/gcc/tree-tsan.** >> c#newcode413<http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode413> >> gcc/tree-tsan.c:413: static const char * >> Missing documentation. >> > > Done. > Do I need to document all function and function parameters (including > trivial)? > Yes. > > > >> >> http://codereview.appspot.com/**5303083/diff/1/gcc/tree-tsan.** >> c#newcode459<http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode459> >> gcc/tree-tsan.c:459: s = NULL; >> MODIFY_EXPR? directly use gimple_build_assign. >> > > I've figured out that a single tree can produce several gimples due to > limitations on gimples (e.g. a function call may have argument calculations > as separate gimples). So I've concluded that the safe way to do it is to > build a full tree first and then convert it to gimple seq. Isn't it the > right way? What is the idiomatic way to build statements? > > Before build gimple assignment, use force_gimple_operand first. > > > Here are some additional concerns: > Please take a look at pass.properties_required and .todo_flags_finish. I > am not sure what needs to be there. After experiencing a lot of compiler > crashes I incrementally pull there basically everything (update and verify > everything just in case...) > > Can GIMPLE_COND contain memory accesses? Since GIMPLE_CALL can't I suspect > that GIMPLE_COND can't as well. > > No, it can not. > There is some clumsy code in is_vtbl_read () and is_dtor_vptr_store (), is > there a better to way to detect accesses to vptr and vtbl? > > Yes, that will be done in the second round of review. Please update your patch set with the modifications. thanks, David
Sign in to reply to this message.
that means some existing bugs get exposed. Your previous version simply skipped the target mem refs. You will need to debug the problem a little more. David On Tue, Nov 1, 2011 at 5:26 AM, <dvyukov@google.com> wrote: > On 2011/10/31 06:08:34, davidxl wrote: >> >> http://codereview.appspot.com/5303083/diff/1/gcc/passes.c#newcode1423 >> gcc/passes.c:1423: NEXT_PASS (pass_tsan); >> Move this to the same place as asan. Otherwise TARGET_MEM_REF won't be > > handled. > > After I moved the pass it started crashing: > Program received signal SIGSEGV, Segmentation fault. > 0x0000000000718f94 in is_gimple_reg_type (t=0x7ffff771efa0) > at gimple.c:2960 > 2960 return !AGGREGATE_TYPE_P (type); > (gdb) bt > #0 0x0000000000718f94 in is_gimple_reg_type (t=0x7ffff771efa0) > at gimple.c:2960 > #1 is_gimple_val (t=0x7ffff771efa0) at gimple.c:3028 > #2 0x00000000008a5d20 in verify_types_in_gimple_reference > (expr=0x7ffff76b74c0, require_lvalue=false) > at tree-cfg.c:2934 > #3 0x00000000008b2d4f in verify_gimple_in_cfg (fn=0x7ffff77c67e0) > at tree-cfg.c:4382 > #4 0x0000000000a061d6 in verify_ssa (check_modified_stmt=true) > at tree-ssa.c:924 > #5 0x00000000007f755c in execute_function_todo (data=Unhandled dwarf > expression opcode 0xf3 > ) > at passes.c:1727 > #6 0x00000000007f7e4d in execute_todo (flags=34854) at passes.c:1758 > #7 0x00000000007fafda in execute_one_pass (pass=0x122a900) > at passes.c:2104 > > The code seems to be (however I am not 100% sure): > D.3617_33 = MEM[(const uint64_t *)ctx_11(D)].nhkey[D.3612_26]{lb: > D.3810_279 sz: 8}; > > > http://codereview.appspot.com/5303083/ >
Sign in to reply to this message.
http://codereview.appspot.com/5303083/diff/3002/gcc/tree-tsan.c File gcc/tree-tsan.c (right): http://codereview.appspot.com/5303083/diff/3002/gcc/tree-tsan.c#newcode1075 gcc/tree-tsan.c:1075: for (eidx = 0; VEC_iterate (edge, exit_bb->preds, eidx, e); eidx++) Use FOR_EACH_EDGE macro http://codereview.appspot.com/5303083/diff/3002/gcc/tree-tsan.c#newcode1082 gcc/tree-tsan.c:1082: gsi_insert_seq_before (&gsi, post_func_seq, GSI_SAME_STMT); On 2011/11/01 11:39:49, dvyukov wrote: > Do I need to make a copy of POST_FUNC_SEQ here? > I think that I do not need location info for this code at all, so is it OK to > leave the seq w/o location and then insert it into several basic blocks? Yes, do not share gimple stmts.
Sign in to reply to this message.
http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c File gcc/tree-tsan.c (right): http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode432 gcc/tree-tsan.c:432: /* Builds either (__tsan_shadow_stack += 1) or (__tsan_shadow_stack -= 1) expression Line too long. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode464 gcc/tree-tsan.c:464: /* Builds either (__tsan_thread_ignore += 1) or (__tsan_thread_ignore -= 1) expression Long line. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode623 gcc/tree-tsan.c:623: && strcmp (decl_name (cfun->decl), "__base_dtor ") == 0) How about inline instance of dtor? http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode625 gcc/tree-tsan.c:625: tree comp = expr->exp.operands [0]; Use TREE_OPERAND http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode627 gcc/tree-tsan.c:627: comp = comp->exp.operands [0]; Use TREE_OPERAND http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode631 gcc/tree-tsan.c:631: if (TREE_CODE (comp) == SSA_NAME) All the above can be simplified using get_base_addr http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode638 gcc/tree-tsan.c:638: "_vptr.", sizeof ("_vptr.") - 1) == 0) This is a very hacky way of recognizing vptr field. C++ FE provides TYPE_VFIELD macro to get the vptr field, but you will need to add a new langhook for it -- which is not liked in upstream -- so the hacky way may be ok (as it is for error checking purpose). http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode646 gcc/tree-tsan.c:646: /* Checks as to whether expr refers to a read from vtlb. vtlb -- vtbl. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode663 gcc/tree-tsan.c:663: tree ref_target = expr->exp.operands [0]; TREE_OPERAND http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode666 gcc/tree-tsan.c:666: gimple ref_stmt = ref_target->ssa_name.def_stmt; Use SSA_NAME_DEF_STMT http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode685 gcc/tree-tsan.c:685: } Please refactor the is_dtor_vtpr_store code above for a new utility function -- is_vptr_access. http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode900 gcc/tree-tsan.c:900: { Remove {} http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode911 gcc/tree-tsan.c:911: bbd->has_sb = 0; Where is the code that instrument function calls?
Sign in to reply to this message.
Hi, sorry that I'm not using the fancy web tool but I do not want to use my google account and gmail address in particular for work-related stuff. On Tue, Nov 01, 2011 at 06:05:46PM +0000, davidxl@google.com wrote: > ... > > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode638 > gcc/tree-tsan.c:638: "_vptr.", sizeof ("_vptr.") - 1) == 0) > This is a very hacky way of recognizing vptr field. C++ FE provides > TYPE_VFIELD macro to get the vptr field, but you will need to add a new > langhook for it -- which is not liked in upstream -- so the hacky way > may be ok (as it is for error checking purpose). > If you have a FIELD_DECL and want to check whether it is a VPTR, you can simply use DECL_VIRTUAL_P. Martin
Sign in to reply to this message.
On 11-11-01 15:26 , Martin Jambor wrote: > Hi, > > sorry that I'm not using the fancy web tool but I do not want to use > my google account and gmail address in particular for work-related > stuff. No worries. You do not need to use the web tool at all. You can simply reply to these messages. As long as you keep reply@codereview.appspotmail.com in the CC and do not remove the "(issue NNNNNN)" string from the subject, your message will be added to the issue log (similarly to how bugzilla works). Diego.
Sign in to reply to this message.
On Tue, Nov 1, 2011 at 12:26 PM, Martin Jambor <mjambor@suse.cz> wrote: > Hi, > > sorry that I'm not using the fancy web tool but I do not want to use > my google account and gmail address in particular for work-related > stuff. > > On Tue, Nov 01, 2011 at 06:05:46PM +0000, davidxl@google.com wrote: >> > > ... > >> >> http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode638 >> gcc/tree-tsan.c:638: "_vptr.", sizeof ("_vptr.") - 1) == 0) >> This is a very hacky way of recognizing vptr field. C++ FE provides >> TYPE_VFIELD macro to get the vptr field, but you will need to add a new >> langhook for it -- which is not liked in upstream -- so the hacky way >> may be ok (as it is for error checking purpose). >> > > If you have a FIELD_DECL and want to check whether it is a VPTR, you > can simply use DECL_VIRTUAL_P. ah yes, that will do. thanks, David > > Martin > >
Sign in to reply to this message.
Fixes after davidxl review. The patch is for google/main branch. 2011-11-03 Dmitriy Vyukov <dvyukov@google.com> * gcc/doc/invoke.texi: * gcc/tree-tsan.c (enum tsan_ignore_type): (struct bb_data): (struct mop_desc): (struct tsan_ignore_desc): (lookup_name): (build_var_decl): (get_shadow_stack_decl): (get_thread_ignore_decl): (get_handle_mop_decl): (ignore_append): (ignore_match): (ignore_load): (tsan_ignore): (build_stack_op): (build_rec_ignore_op): (build_stack_assign): (instr_mop): (instr_vptr_store): (is_func_instrumentation_required): (build_func_entry_instr): (build_func_exit_instr): (set_location): (is_vptr_store): (is_load_of_const): (handle_expr): (handle_gimple): (instrument_bblock): (instrument_mops): (instrument_func_entry): (instrument_func_exit): (tsan_pass): (tsan_gate): * gcc/tree-pass.h: * gcc/testsuite/gcc.dg/tsan-ignore.ignore: * gcc/testsuite/gcc.dg/tsan.h (__tsan_init): (__tsan_expect_mop): (__tsan_handle_mop): * gcc/testsuite/gcc.dg/tsan-ignore.c (foo): (int bar): (int baz): (int bla): (int xxx): (main): * gcc/testsuite/gcc.dg/tsan-ignore.h (in_tsan_ignore_header): * gcc/testsuite/gcc.dg/tsan-stack.c (void barbaz): (foobar): * gcc/testsuite/gcc.dg/tsan-mop.c (struct C): (struct P): (void teststructp): (void testfor): (teststruct): * gcc/common.opt: * gcc/Makefile.in: * gcc/passes.c: Index: gcc/doc/invoke.texi =================================================================== --- gcc/doc/invoke.texi (revision 180522) +++ gcc/doc/invoke.texi (working copy) @@ -308,6 +308,7 @@ -fdump-tree-ssa@r{[}-@var{n}@r{]} -fdump-tree-pre@r{[}-@var{n}@r{]} @gol -fdump-tree-ccp@r{[}-@var{n}@r{]} -fdump-tree-dce@r{[}-@var{n}@r{]} @gol -fdump-tree-gimple@r{[}-raw@r{]} -fdump-tree-mudflap@r{[}-@var{n}@r{]} @gol +-fdump-tree-tsan@r{[}-@var{n}@r{]} @gol -fdump-tree-dom@r{[}-@var{n}@r{]} @gol -fdump-tree-dse@r{[}-@var{n}@r{]} @gol -fdump-tree-phiprop@r{[}-@var{n}@r{]} @gol @@ -381,8 +382,8 @@ -floop-parallelize-all -flto -flto-compression-level @gol -flto-partition=@var{alg} -flto-report -fmerge-all-constants @gol -fmerge-constants -fmodulo-sched -fmodulo-sched-allow-regmoves @gol --fmove-loop-invariants fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol --fno-default-inline @gol +-fmove-loop-invariants -fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol +-ftsan -ftsan-ignore -fno-default-inline @gol -fno-defer-pop -fno-function-cse -fno-guess-branch-probability @gol -fno-inline -fno-math-errno -fno-peephole -fno-peephole2 @gol -fno-sched-interblock -fno-sched-spec -fno-signed-zeros @gol @@ -5896,6 +5897,11 @@ Dump each function after adding mudflap instrumentation. The file name is made by appending @file{.mudflap} to the source file name. +@item tsan +@opindex fdump-tree-tsan +Dump each function after adding ThreadSanitizer instrumentation. The file name is +made by appending @file{.tsan} to the source file name. + @item sra @opindex fdump-tree-sra Dump each function after performing scalar replacement of aggregates. The @@ -6674,6 +6680,12 @@ some protection against outright memory corrupting writes, but allows erroneously read data to propagate within a program. +@item -ftsan -ftsan-ignore +@opindex ftsan +@opindex ftsan-ignore +Add ThreadSanitizer instrumentation. Use @option{-ftsan-ignore} to specify +an ignore file. Refer to http://go/tsan for details. + @item -fthread-jumps @opindex fthread-jumps Perform optimizations where we check to see if a jump branches to a Index: gcc/tree-tsan.c =================================================================== --- gcc/tree-tsan.c (revision 0) +++ gcc/tree-tsan.c (revision 0) @@ -0,0 +1,1075 @@ +/* ThreadSanitizer instrumentation pass. + http://code.google.com/p/data-race-test + Copyright (C) 2011 + Free Software Foundation, Inc. + Contributed by Dmitry Vyukov <dvyukov@google.com> + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "intl.h" +#include "tm.h" +#include "basic-block.h" +#include "gimple.h" +#include "function.h" +#include "tree-flow.h" +#include "tree-pass.h" +#include "cfghooks.h" +#include "langhooks.h" +#include "output.h" +#include "options.h" +#include "target.h" +#include "cgraph.h" +#include "diagnostic.h" + +#include <stdlib.h> +#include <stdio.h> + +/* ThreadSanitizer is a data race detector for C/C++ programs. + http://code.google.com/p/data-race-test/wiki/ThreadSanitizer + + The tool consists of two parts: + instrumentation module (this file) and a run-time library. + The instrumentation module maintains shadow call stacks + and intercepts interesting memory accesses. + The instrumentation is enabled with -ftsan flag. + + Instrumentation for shadow stack maintenance is as follows: + void somefunc () + { + __tsan_shadow_stack [-1] = __builtin_return_address (0); + __tsan_shadow_stack++; + // function body + __tsan_shadow_stack--; + } + + Interception for memory access interception is as follows: + *addr = 1; + __tsan_handle_mop (addr, flags); + where flags are (is_sblock | (is_store << 1) | ((sizeof (*addr) - 1) << 2). + is_sblock is used merely for optimization purposes and can always + be set to 1, see comments in instrument_mops function. + + Ignore files can be used to selectively non instrument some functions. + Ignore file is specified with -ftsan-ignore=filename flag. + There are 3 types of ignores: (1) do not instrument memory accesses + in the function, (2) do not create sblocks in the function + and (3) recursively ignore memory accesses in the function. + That last ignore type requires additional instrumentation of the form: + void somefunc () + { + __tsan_thread_ignore++; + // function body + __tsan_thread_ignore--; + } + + The run-time library provides __tsan_handle_mop function, + definitions of __tsan_shadow_stack and __tsan_thread_ignore variables, + and intercepts synchronization related functions. */ + +#define TSAN_IGNORE "__tsan_thread_ignore" +#define TSAN_STACK "__tsan_shadow_stack" +#define TSAN_MOP "__tsan_handle_mop" +#define TSAN_PERFIX "__tsan_" +#define MAX_MOP_BYTES 16 +#define SBLOCK_SIZE 5 + +enum tsan_ignore_type +{ + tsan_ignore_none = 1 << 0, /* Do not ignore. */ + tsan_ignore_func = 1 << 1, /* Completely ignore the whole func. */ + tsan_ignore_mop = 1 << 2, /* Do not instrument accesses. */ + tsan_ignore_rec = 1 << 3, /* Do not instrument accesses recursively. */ + tsan_ignore_hist = 1 << 4 /* Do not create superblocks. */ +}; + +/* Info associated with each basic block. + Used to determine super-blocks (see instrument_mops ()). */ + +struct bb_data +{ + int is_visited; + int has_sb; + const char *sb_file; + int sb_line_min; + int sb_line_max; +}; + +/* Memory access descriptor. */ + +struct mop_desc +{ + int is_call; + int is_store; + gimple_stmt_iterator gsi; + tree expr; +}; + +/* Descriptor of an ignore file entry. */ + +struct tsan_ignore_desc +{ + struct tsan_ignore_desc *next; + enum tsan_ignore_type type; + char *name; +}; + +/* Number of instrumented memory accesses in the current function. */ + +static int func_mops; + +/* Number of function calls in the current function. */ + +static int func_calls; + +/* Ignore status for the current function (see tsan_ignore_type). */ + +static enum tsan_ignore_type func_ignore; + +static int ignore_init = 0; +static struct tsan_ignore_desc *ignore_head; + +typedef struct mop_desc mop_desc; +DEF_VEC_O (mop_desc); +DEF_VEC_ALLOC_O (mop_desc, heap); +static VEC (mop_desc, heap) *mop_list; + +tree __attribute__ ((weak)) +lookup_name (tree t) +{ + (void)t; + return NULL_TREE; +} + +/* Returns a definition of a runtime variable with type TYP and name NAME. */ + +static tree +build_var_decl (tree typ, const char *name) +{ + tree id; + tree decl; + varpool_node_ptr var; + + /* Check if a user has defined it for testing. */ + id = get_identifier (name); + var = varpool_node_for_asm (id); + if (var != NULL) + { + decl = var->decl; + gcc_assert (TREE_CODE (decl) == VAR_DECL); + return decl; + } + + decl = build_decl (UNKNOWN_LOCATION, VAR_DECL, id, typ); + TREE_STATIC (decl) = 1; + TREE_PUBLIC (decl) = 1; + DECL_EXTERNAL (decl) = 1; + if (targetm.have_tls) + DECL_TLS_MODEL (decl) = decl_default_tls_model (decl); + TREE_USED (decl) = 1; + TREE_THIS_VOLATILE (decl) = 1; + SET_DECL_ASSEMBLER_NAME (decl, id); + return decl; +} + +/* Builds the following decl + extern __thread void **__tsan_shadow_stack; */ + +static tree +get_shadow_stack_decl (void) +{ + static tree decl; + + if (decl == NULL) + decl = build_var_decl (build_pointer_type (ptr_type_node), TSAN_STACK); + return decl; +} + +/* Builds the following decl + extern __thread int __tsan_thread_ignore; */ + +static tree +get_thread_ignore_decl (void) +{ + static tree decl; + + if (decl == NULL) + decl = build_var_decl (integer_type_node, TSAN_IGNORE); + return decl; +} + +/* Builds the following decl + void __tsan_handle_mop (void *addr, unsigned flags); */ + +static tree +get_handle_mop_decl (void) +{ + tree id; + tree fn_type; + varpool_node_ptr var; + static tree decl; + + if (decl != NULL) + return decl; + + /* Check if a user has defined it for testing. */ + id = get_identifier (TSAN_MOP); + var = varpool_node_for_asm (id); + if (var != NULL) + { + decl = var->decl; + gcc_assert (TREE_CODE (decl) == FUNCTION_DECL); + return decl; + } + + fn_type = build_function_type_list (void_type_node, ptr_type_node, + integer_type_node , NULL_TREE); + decl = build_fn_decl (TSAN_MOP, fn_type); + TREE_NOTHROW (decl) = 1; + DECL_ATTRIBUTES (decl) = tree_cons (get_identifier ("leaf"), + NULL, DECL_ATTRIBUTES (decl)); + DECL_ASSEMBLER_NAME (decl); + return decl; +} + +/* Adds new ignore definition to the global list. + TYPE is the ignore type (see tsan_ignore_type). + NAME is the ignore pattern (e.g. "std*string*insert"). */ + +static void +ignore_append (enum tsan_ignore_type type, char *name) +{ + struct tsan_ignore_desc *desc; + + desc = XCNEW (struct tsan_ignore_desc); + desc->type = type; + desc->name = xstrdup (name); + desc->next = ignore_head; + ignore_head = desc; +} + +/* Checks as to whether identifier STR matches template TEMPL. + Templates can only contain '*', e.g. 'std*string*insert'. + Templates implicitly start and end with '*' + since they are matched against mangled names. + Returns non-zero if STR is matched against TEMPL. */ + +static int +ignore_match (char *templ, const char *str) +{ + char *tpos; + const char *spos; + + while (templ && templ [0]) + { + if (templ [0] == '*') + { + templ++; + continue; + } + if (str [0] == 0) + return 0; + tpos = strchr (templ, '*'); + if (tpos != NULL) + tpos [0] = 0; + spos = strstr (str, templ); + str = spos + strlen (templ); + templ = tpos; + if (tpos != NULL) + tpos [0] = '*'; + if (spos == NULL) + return 0; + } + return 1; +} + +/* Loads ignore definitions from the file specified by -ftsan-ignore=filename. + The result is stored in the global ignore_head list. + Ignore files have the following format: + +# This is a comment - ignored + +# The below line says to not instrument memory accesses +# in all functions that match 'std*string*insert' +fun:std*string*insert + +# The below line says to not instrument memory accesses +# in the function called 'foobar' *and* in all functions +# that it calls recursively +fun_r:foobar + +# The below line says to not create superblocks +# in the function called 'barbaz' +fun_hist:barbaz + +# Ignore all functions in the source file +src:atomic.c + +# Everything else is uninteresting for us (e.g. obj:) +*/ + +static void +ignore_load (void) +{ + FILE *f; + char *line; + size_t linesz; + ssize_t sz; + char buf [PATH_MAX]; + + if(getenv("GCCTSAN_PAUSE")) + { + int res; + printf("ATTACH A DEBUGGER AND PRESS ENTER\n"); + res = scanf("%s", buf); + (void)res; + } + + if (flag_tsan_ignore == NULL || flag_tsan_ignore [0] == 0) + return; + + f = fopen (flag_tsan_ignore, "r"); + if (f == NULL) + { + /* Try to open it relative to main_input_filename. */ + strncpy (buf, main_input_filename, sizeof (buf)); + buf [sizeof (buf) - 1] = 0; + line = strrchr (buf, '/'); + if (line != NULL) + { + line++; + strncpy (line, flag_tsan_ignore, sizeof (buf) - (line - buf)); + buf [sizeof (buf) - 1] = 0; + f = fopen (buf, "r"); + } + } + if (f == NULL) + { + error ("failed to open ignore file '%s'\n", flag_tsan_ignore); + return; + } + + line = 0; + linesz = 0; + while ((sz = getline (&line, &linesz, f)) != -1) + { + if (sz == 0) + continue; + /* Strip line terminator. */ + if (line [sz - 1] == '\r' || line [sz - 1] == '\n') + line [sz - 1] = 0; + if (strncmp (line, "src:", sizeof ("src:") - 1) == 0) + ignore_append (tsan_ignore_func, line + sizeof ("src:") - 1); + else if (strncmp (line, "fun:", sizeof ("fun:") - 1) == 0) + ignore_append (tsan_ignore_mop, line + sizeof ("fun:") - 1); + else if (strncmp (line, "fun_r:", sizeof ("fun_r:") - 1) == 0) + ignore_append (tsan_ignore_rec, line + sizeof ("fun_r:") - 1); + else if (strncmp (line, "fun_hist:", sizeof ("fun_hist:") - 1) == 0) + ignore_append (tsan_ignore_hist, line + sizeof ("fun_hist:") - 1); + /* Other lines are not interesting. */ + } + + free (line); + fclose (f); +} + +/* Returns ignore status for the current function. */ + +static enum tsan_ignore_type +tsan_ignore (void) +{ + const char *func_name; + const char *src_name; + struct tsan_ignore_desc *desc; + + if (ignore_init == 0) + { + ignore_load (); + ignore_init = 1; + } + + src_name = expand_location (cfun->function_start_locus).file; + if (src_name == NULL) + src_name = ""; + + func_name = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (cfun->decl)); + /* Ignore all functions starting with __tsan_ - intended for testing. */ + if (strncmp (func_name, TSAN_PERFIX, sizeof (TSAN_PERFIX) - 1) == 0) + return tsan_ignore_func; + + for (desc = ignore_head; desc; desc = desc->next) + { + if (desc->type == tsan_ignore_func) + { + if (ignore_match (desc->name, src_name)) + return desc->type; + } + else if (ignore_match (desc->name, func_name)) + return desc->type; + } + return tsan_ignore_none; +} + +/* Builds either (__tsan_shadow_stack += 1) or (__tsan_shadow_stack -= 1) + expression depending on DO_DEC parameter. Appends the result to SEQ. */ + +static void +build_stack_op (gimple_seq *seq, bool do_dec) +{ + tree op_size; + double_int op_size_cst; + unsigned long long size_val; + unsigned long long size_valhi; + tree op_expr; + gimple assign; + tree sstack_decl; + gimple_seq s; + + op_size = TYPE_SIZE (ptr_type_node); + op_size_cst = tree_to_double_int (op_size); + size_val = op_size_cst.low / BITS_PER_UNIT; + size_valhi = 0; + if (do_dec) + { + size_val = -size_val; + size_valhi = -1; + } + op_size = build_int_cst_wide (sizetype, size_val, size_valhi); + sstack_decl = get_shadow_stack_decl (); + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, sstack_decl, op_size); + + s = NULL; + op_expr = force_gimple_operand (op_expr, &s, true, NULL_TREE); + gimple_seq_add_seq (seq, s); + + assign = gimple_build_assign (sstack_decl, op_expr); + gimple_seq_add_stmt (seq, assign); +} + +/* Builds either (__tsan_thread_ignore += 1) or (__tsan_thread_ignore -= 1) + expression depending on OP parameter. Stores the result in SEQ. */ + +static void +build_rec_ignore_op (gimple_seq *seq, enum tree_code op) +{ + tree rec_expr; + gimple_seq rec_inc; + gimple rec_assign; + tree ignore_decl; + + ignore_decl = get_thread_ignore_decl (); + rec_expr = build2 (op, integer_type_node, ignore_decl, integer_one_node); + rec_inc = NULL; + rec_expr = force_gimple_operand (rec_expr, &rec_inc, true, NULL_TREE); + gimple_seq_add_seq (seq, rec_inc); + + rec_inc = NULL; + ignore_decl = force_gimple_operand (ignore_decl, &rec_inc, true, NULL_TREE); + gimple_seq_add_seq (seq, rec_inc); + + rec_assign = gimple_build_assign (ignore_decl, rec_expr); + gimple_seq_add_stmt (seq, rec_assign); +} + +/* Build the following gimple sequence: + __tsan_shadow_stack [-1] = __builtin_return_address (0); + Stores the result in SEQ. */ + +static void +build_stack_assign (gimple_seq *seq) +{ + tree pc_addr; + tree op_size; + tree op_expr; + tree stack_op; + tree retaddr_decl; + tree assign; + + retaddr_decl = implicit_built_in_decls [BUILT_IN_RETURN_ADDRESS]; + pc_addr = build_call_expr (retaddr_decl, 1, integer_zero_node); + op_size = build_int_cst_wide (sizetype, -(POINTER_SIZE / BITS_PER_UNIT), -1); + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, + get_shadow_stack_decl (), op_size); + stack_op = build1 (INDIRECT_REF, ptr_type_node, op_expr); + assign = build2 (MODIFY_EXPR, ptr_type_node, stack_op, pc_addr); + force_gimple_operand (assign, seq, true, NULL_TREE); +} + +/* Builds the following gimple sequence: + __tsan_handle_mop (&EXPR, + (IS_SBLOCK | (IS_STORE << 1) | ((sizeof (EXPR) - 1) << 2); + The result is stored in GSEQ. */ + +static void +instr_mop (tree expr, int is_store, int is_sblock, gimple_seq *gseq) +{ + tree addr_expr; + tree expr_type; + unsigned size; + unsigned flags; + tree flags_expr; + tree call_expr; + + gcc_assert (gseq != 0 && *gseq == 0); + gcc_assert (is_gimple_addressable (expr)); + + addr_expr = build_addr (expr, current_function_decl); + expr_type = TREE_TYPE (expr); + while (TREE_CODE (expr_type) == ARRAY_TYPE) + expr_type = TREE_TYPE (expr_type); + size = TREE_INT_CST_LOW (TYPE_SIZE (expr_type)); + size = size / BITS_PER_UNIT; + if (size > MAX_MOP_BYTES) + size = MAX_MOP_BYTES; + size -= 1; + flags = ((!!is_sblock << 0) + (!!is_store << 1) + (size << 2)); + flags_expr = build_int_cst (unsigned_type_node, flags); + call_expr = build_call_expr (get_handle_mop_decl (), + 2, addr_expr, flags_expr); + force_gimple_operand (call_expr, gseq, true, 0); +} + +/* Builds the following gimple sequence: + int is_store = (EXPR != RHS); // The temp is not actually introduced. + __tsan_handle_mop (&EXPR, + (IS_SBLOCK | (IS_STORE << 1) | ((sizeof (EXPR) - 1) << 2); + The result is stored in GSEQ. */ + +static void +instr_vptr_store (tree expr, tree rhs, int is_sblock, gimple_seq *gseq) +{ + tree expr_ptr; + tree addr_expr; + tree expr_type; + tree expr_size; + double_int size; + unsigned flags; + tree flags_expr; + gimple_seq flags_seq; + gimple collect; + tree is_store_expr; + + expr_ptr = build_addr (expr, current_function_decl); + addr_expr = force_gimple_operand (expr_ptr, gseq, true, NULL_TREE); + expr_type = TREE_TYPE (expr); + while (TREE_CODE (expr_type) == ARRAY_TYPE) + expr_type = TREE_TYPE (expr_type); + expr_size = TYPE_SIZE (expr_type); + size = tree_to_double_int (expr_size); + gcc_assert (size.high == 0 && size.low != 0); + if (size.low > 128) + size.low = 128; + size.low = (size.low / 8) - 1; + flags = ((!!is_sblock << 0) + (size.low << 2)); + flags_expr = build_int_cst (unsigned_type_node, flags); + is_store_expr = build2 (NE_EXPR, unsigned_type_node, + build1 (VIEW_CONVERT_EXPR, size_type_node, expr), + build1 (VIEW_CONVERT_EXPR, size_type_node, rhs)); + is_store_expr = build2 (LSHIFT_EXPR, unsigned_type_node, + is_store_expr, integer_one_node); + flags_expr = build2 (BIT_IOR_EXPR, unsigned_type_node, + is_store_expr, flags_expr); + flags_seq = 0; + flags_expr = force_gimple_operand (flags_expr, &flags_seq, true, NULL_TREE); + gimple_seq_add_seq (gseq, flags_seq); + collect = gimple_build_call ( + get_handle_mop_decl (), 2, addr_expr, flags_expr); + gimple_seq_add_stmt (gseq, collect); +} + +/* Returns true if function entry and exit need to be instrumented. */ + +static bool +is_func_instrumentation_required (void) +{ + if (func_calls == 0 && func_mops == 0) + return false; + if (func_ignore != tsan_ignore_rec) + return true; + if (func_ignore == tsan_ignore_rec && func_calls != 0) + return true; + return false; +} + +/* Returns gimple seq that needs to be inserted at function entry. */ + +static gimple_seq +build_func_entry_instr (void) +{ + gimple_seq gs; + + gs = NULL; + gcc_assert (is_func_instrumentation_required ()); + if (func_ignore != tsan_ignore_rec) + { + build_stack_assign (&gs); + build_stack_op (&gs, false); + } + else + build_rec_ignore_op (&gs, PLUS_EXPR); + return gs; +} + +/* Returns gimple seq that needs to be inserted before function exit. */ + +static gimple_seq +build_func_exit_instr (void) +{ + gimple_seq gs; + + gs = NULL; + gcc_assert (is_func_instrumentation_required ()); + if (func_ignore != tsan_ignore_rec) + build_stack_op (&gs, true); + else + build_rec_ignore_op (&gs, MINUS_EXPR); + return gs; +} + +/* Sets location LOC for all gimples in the SEQ. */ + +static void +set_location (gimple_seq seq, location_t loc) +{ + gimple_seq_node n; + + for (n = gimple_seq_first (seq); n != NULL; n = n->next) + gimple_set_location (n->stmt, loc); + verify_gimple_in_seq (seq); +} + +/* Check as to whether EXPR refers to a store to vptr. */ + +static tree +is_vptr_store (gimple stmt, tree expr, int is_store) +{ + if (is_store == 1 + && gimple_assign_single_p (stmt) + && TREE_CODE (expr) == COMPONENT_REF) + { + tree field = TREE_OPERAND (expr, 1); + if (TREE_CODE (field) == FIELD_DECL + && DECL_VIRTUAL_P (field)) + return gimple_assign_rhs1 (stmt); + } + return NULL; +} + +/* Checks as to whether EXPR refers to constant var/field/param. + Don't bother to instrument them. */ + +static int +is_load_of_const (tree expr, int is_store) +{ + if (is_store) + return 0; + if (TREE_CODE (expr) == COMPONENT_REF) + expr = TREE_OPERAND (expr, 1); + if (TREE_CODE (expr) == VAR_DECL + || TREE_CODE (expr) == PARM_DECL + || TREE_CODE (expr) == FIELD_DECL) + { + if (TREE_READONLY (expr)) + return 1; + } + return 0; +} + +/* Checks as to whether EXPR needs to be instrumented, + if so puts it into the MOP_LIST. + GSI is the iterator from which EXPR was extracted. + IS_STORE says as to whether EXPR refers to a memory store + or a memory load. */ + +static void +handle_expr (gimple_stmt_iterator gsi, tree expr, int is_store, + VEC (mop_desc, heap) **mop_list) +{ + enum tree_code tcode; + struct mop_desc mop; + unsigned fld_off; + unsigned fld_size; + + tcode = TREE_CODE (expr); + + /* Below are things we do not instrument + (no possibility of races or not implemented yet). */ + if ((func_ignore & (tsan_ignore_mop | tsan_ignore_rec)) + || get_base_address (expr) == NULL + /* Compiler-emitted artificial variables. */ + || (DECL_P (expr) && DECL_ARTIFICIAL (expr)) + /* The var does not live in memory -> no possibility of races. */ + || (tcode == VAR_DECL + && TREE_ADDRESSABLE (expr) == 0 + && DECL_EXTERNAL (expr) == 0) + /* TODO (dvyukov): not implemented. */ + || TREE_CODE (TREE_TYPE (expr)) == RECORD_TYPE + /* TODO (dvyukov): not implemented. */ + || tcode == CONSTRUCTOR + /* TODO (dvyukov): not implemented. */ + || tcode == PARM_DECL + /* Load of a const variable/parameter/field. */ + || is_load_of_const (expr, is_store)) + return; + + if (tcode == COMPONENT_REF) + { + tree field = TREE_OPERAND (expr, 1); + if (TREE_CODE (field) == FIELD_DECL) + { + fld_off = TREE_INT_CST_LOW (DECL_FIELD_BIT_OFFSET (field)); + fld_size = TREE_INT_CST_LOW (DECL_SIZE (field)); + if (((fld_off % BITS_PER_UNIT) != 0) + || ((fld_size % BITS_PER_UNIT) != 0)) + { + /* As of now it crashes compilation. + TODO (dvyukov): handle bit-fields as if touching + the whole field. */ + return; + } + } + } + + /* TODO (dvyukov): handle other cases + (FIELD_DECL, MEM_REF, ARRAY_RANGE_REF, TARGET_MEM_REF, ADDR_EXPR). */ + if (tcode != ARRAY_REF + && tcode != VAR_DECL + && tcode != COMPONENT_REF + && tcode != INDIRECT_REF + && tcode != MEM_REF) + return; + + mop.is_call = 0; + mop.gsi = gsi; + mop.expr = expr; + mop.is_store = is_store; + VEC_safe_push (mop_desc, heap, *mop_list, &mop); +} + +/* Collects all interesting memory accesses from the gimple pointed to by GSI + into MOP_LIST. */ + +static void +handle_gimple (gimple_stmt_iterator gsi, VEC (mop_desc, heap) **mop_list) +{ + unsigned i; + struct mop_desc mop; + gimple stmt; + enum gimple_code gcode; + tree rhs; + tree lhs; + + stmt = gsi_stmt (gsi); + gcode = gimple_code (stmt); + if (gcode >= LAST_AND_UNUSED_GIMPLE_CODE) + return; + + switch (gcode) + { + case GIMPLE_CALL: + { + func_calls += 1; + memset (&mop, 0, sizeof (mop)); + mop.is_call = 1; + VEC_safe_push (mop_desc, heap, *mop_list, &mop); + break; + } + + case GIMPLE_ASSIGN: + { + /* Handle assignment lhs as store. */ + lhs = gimple_assign_lhs (stmt); + handle_expr (gsi, lhs, 1, mop_list); + + /* Handle operands as loads. */ + for (i = 1; i < gimple_num_ops (stmt); i++) + { + rhs = gimple_op (stmt, i); + handle_expr (gsi, rhs, 0, mop_list); + } + break; + } + + default: + break; + } +} + +/* Instruments single basic block BB. + BBD is the sblock info associated with the block. */ + +static void +instrument_bblock (struct bb_data *bbd, basic_block bb) +{ + int ix; + int is_sblock; + gimple_stmt_iterator gsi; + struct mop_desc *mop; + gimple stmt; + location_t loc; + expanded_location eloc; + gimple_seq instr_seq; + tree rhs; + + /* Iterate over all gimples and collect interesting mops into mop_list. */ + VEC_free (mop_desc, heap, mop_list); + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + handle_gimple (gsi, &mop_list); + } + + mop = 0; + for (ix = 0; VEC_iterate (mop_desc, mop_list, ix, mop); ix += 1) + { + if (mop->is_call != 0) + { + /* After a function call we must start a brand new sblock, + because the function can contain synchronization. */ + bbd->has_sb = 0; + continue; + } + + func_mops += 1; + stmt = gsi_stmt (mop->gsi); + loc = gimple_location (stmt); + eloc = expand_location (loc); + + /* Check as to whether we may not set sblock flag for the access. */ + is_sblock = (bbd->has_sb == 0 + || !(eloc.file != 0 + && bbd->sb_file != 0 + && strcmp (eloc.file, bbd->sb_file) == 0 + && eloc.line >= bbd->sb_line_min + && eloc.line <= bbd->sb_line_max)); + + if (func_ignore == tsan_ignore_hist) + is_sblock = 0; + + if (is_sblock) + { + /* Start new sblock with new source info. */ + bbd->has_sb = 1; + bbd->sb_file = eloc.file; + bbd->sb_line_min = eloc.line; + bbd->sb_line_max = eloc.line + SBLOCK_SIZE; + } + + instr_seq = 0; + rhs = is_vptr_store (stmt, mop->expr, mop->is_store); + if (rhs == NULL) + instr_mop (mop->expr, mop->is_store, is_sblock, &instr_seq); + else + instr_vptr_store (mop->expr, rhs, is_sblock, &instr_seq); + gcc_assert (instr_seq != 0); + set_location (instr_seq, loc); + /* Instrumentation for assignment of a function result + must be inserted after the call. Instrumentation for + reads of function arguments must be inserted before the call. + That's because the call can contain synchronization. */ + if (is_gimple_call (stmt) && mop->is_store == 1) + gsi_insert_seq_after (&mop->gsi, instr_seq, GSI_NEW_STMT); + else + gsi_insert_seq_before (&mop->gsi, instr_seq, GSI_SAME_STMT); + } +} + +/* Instruments all interesting memory accesses in the current function. */ + +static void +instrument_mops (void) +{ + basic_block bb; + int *blocks_inverted; + struct bb_data *bb_data; + struct bb_data *pred; + struct bb_data *bbd; + edge e; + edge_iterator ei; + int sb_line_min, sb_line_max; + int cnt, i; + + /* The function does basic block traversal in reverse top sort order + of the inverted CFG. Such order is required to properly mark super-blocks. + The idea behind super-blocks is as follows. + If several memory accesses happen within SBLOCK_SIZE source code lines + from each other, then we only mark the first access as SBLOCK. + This allows the runtime library to memorize a stack trace + only for the first access and do not memorize for others. + This significantly reduces memory consumption in exchange for slightly + imprecise stack traces for previous accesses. */ + + blocks_inverted = XNEWVEC (int, last_basic_block + NUM_FIXED_BLOCKS); + bb_data = XCNEWVEC (struct bb_data, last_basic_block + NUM_FIXED_BLOCKS); + cnt = inverted_post_order_compute (blocks_inverted); + for (i = 0; i < cnt; i++) + { + bb = BASIC_BLOCK (blocks_inverted [i]); + bbd = &bb_data [bb->index]; + /* Iterate over all predecessors and merge their sblock info. */ + FOR_EACH_EDGE (e, ei, bb->preds) + { + pred = &bb_data [e->src->index]; + if (!pred->is_visited || !pred->has_sb || pred == bbd) + { + /* If there is a not visited predecessor, + or a predecessor with no active sblock info, + or a self-loop, then we will have to start + a brand new sblock on next memory access. */ + bbd->has_sb = 0; + break; + } + else if (bbd->has_sb == 0) + { + /* If it's a first predecessor, just copy the info. */ + bbd->has_sb = 1; + bbd->sb_file = pred->sb_file; + bbd->sb_line_min = pred->sb_line_min; + bbd->sb_line_max = pred->sb_line_max; + } + else + { + /* Otherwise, find the interception + between two sblock descriptors. */ + bbd->has_sb = 0; + if (bbd->sb_file != 0 && pred->sb_file != 0 + && strcmp (bbd->sb_file, pred->sb_file) == 0) + { + sb_line_min = MAX (bbd->sb_line_min, pred->sb_line_min); + sb_line_max = MIN (bbd->sb_line_max, pred->sb_line_max); + if (sb_line_min <= sb_line_max) + { + bbd->has_sb = 1; + bbd->sb_line_min = sb_line_min; + bbd->sb_line_max = sb_line_max; + } + } + /* No interception, have to start new sblock. */ + if (bbd->has_sb == 0) + break; + } + } + + instrument_bblock (bbd, bb); + bbd->is_visited = 1; + } + + free (blocks_inverted); + free (bb_data); +} + +/* Instruments function entry. */ + +static void +instrument_func_entry (void) +{ + gimple_seq seq; + basic_block entry_bb; + edge entry_edge; + gimple_stmt_iterator gsi; + + /* Insert new BB before the first BB. */ + seq = build_func_entry_instr (); + gcc_assert (seq != NULL); + entry_bb = ENTRY_BLOCK_PTR; + entry_edge = single_succ_edge (entry_bb); + set_location (seq, cfun->function_start_locus); + entry_bb = split_edge (entry_edge); + gsi = gsi_start_bb (entry_bb); + gsi_insert_seq_after (&gsi, seq, GSI_NEW_STMT); +} + +/* Instruments function exits. */ + +static void +instrument_func_exit (void) +{ + location_t loc; + gimple_seq seq; + basic_block exit_bb; + gimple_stmt_iterator gsi; + gimple stmt; + edge e; + edge_iterator ei; + + /* Find all function exits. */ + exit_bb = EXIT_BLOCK_PTR; + FOR_EACH_EDGE (e, ei, exit_bb->preds) + { + gsi = gsi_last_bb (e->src); + stmt = gsi_stmt (gsi); + gcc_assert (gimple_code (stmt) == GIMPLE_RETURN); + loc = gimple_location (stmt); + seq = build_func_exit_instr (); + gcc_assert (seq != NULL); + set_location (seq, loc); + gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT); + } +} + +/* ThreadSanitizer instrumentation pass. */ + +static unsigned +tsan_pass (void) +{ + func_ignore = tsan_ignore (); + if (func_ignore == tsan_ignore_func) + return 0; + + func_calls = 0; + func_mops = 0; + + instrument_mops (); + + if (is_func_instrumentation_required ()) + { + instrument_func_entry (); + instrument_func_exit (); + } + + return 0; +} + +/* The pass's gate. */ + +static bool +tsan_gate (void) +{ + return flag_tsan != 0; +} + +/* The pass descriptor. */ + +struct gimple_opt_pass pass_tsan = {{ + GIMPLE_PASS, + "tsan", /* name */ + tsan_gate, /* gate */ + tsan_pass, /* execute */ + NULL, /* sub */ + NULL, /* next */ + 0, /* static_pass_number */ + TV_NONE, /* tv_id */ + PROP_trees | PROP_cfg, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + TODO_dump_cgraph | TODO_dump_func | TODO_verify_all + | TODO_update_ssa | TODO_update_address_taken /* todo_flags_finish */ +}}; + Property changes on: gcc/tree-tsan.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/tree-pass.h =================================================================== --- gcc/tree-pass.h (revision 180522) +++ gcc/tree-pass.h (working copy) @@ -352,6 +352,7 @@ extern struct gimple_opt_pass pass_mudflap_1; extern struct gimple_opt_pass pass_mudflap_2; +extern struct gimple_opt_pass pass_tsan; extern struct gimple_opt_pass pass_lower_cf; extern struct gimple_opt_pass pass_refactor_eh; extern struct gimple_opt_pass pass_lower_eh; Index: gcc/testsuite/gcc.dg/tsan-ignore.ignore =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) @@ -0,0 +1,7 @@ +#comment +fun:foo +fun:*bar +fun:baz* +fun:*bla* +fun:x*x +src:tsan-ignore.h Index: gcc/testsuite/gcc.dg/tsan.h =================================================================== --- gcc/testsuite/gcc.dg/tsan.h (revision 0) +++ gcc/testsuite/gcc.dg/tsan.h (revision 0) @@ -0,0 +1,101 @@ +/* Helper declarations and functions for ThreadSanitizer instrumentation (-ftsan) testing */ + +int printf (char *str, ...); +void exit (int); + +/* Variables referenced by the instrumentation. */ + +__thread void **__tsan_shadow_stack; +__thread int __tsan_thread_ignore; + +/* Local helper vars. */ + +__thread void *shadow_stack[1024]; +__thread int pad[1024]; +__thread int mop_expect = 0; +__thread int mop_depth = 0; +__thread void* mop_addr = 0; +__thread unsigned long long mop_pc = 0; +__thread unsigned mop_flags = 0; +__thread unsigned mop_line = 0; + +/* Setups shadow stack var (not instrumented). */ + +void __attribute__ ((constructor)) +__tsan_init (void) +{ + __tsan_shadow_stack = shadow_stack; +} + +/* Declare that we expect an instrumented memory access (not instrumented). + depth - stack depth of the mop (0 - main, 1 - func called from main and so on). + addr - memory access address. + is_store - store/load. + is_sblock - superblock flag of the access. + size - memory access size in bytes. */ + +void +__tsan_expect_mop (int depth, void const volatile *addr, int is_store, + int is_sblock, int size, unsigned line) +{ + if (mop_expect) + { + printf ("missed mop: addr=%p pc=%d line=%d\n", mop_addr, mop_pc, mop_line); + exit (1); + } + + mop_expect = 1; + mop_depth = depth; + mop_addr = (void*)addr; + mop_pc = (unsigned long long)__builtin_return_address(0); + mop_flags = !!is_sblock | (!!is_store << 1) | ((size - 1) << 2); + mop_line = line; +} + +/* Memory access function (referenced by instrumentation, not instrumented). */ + +void +__tsan_handle_mop (void *addr, unsigned flags) +{ + unsigned long long pc; + int depth; + + printf ("mop: addr=%p flags=%x called from %p line=%d\n", + addr, flags, __tsan_shadow_stack [-2], mop_line); + if (mop_expect == 0) + return; + + /* Verify parameters with what we expect. */ + + if (addr != mop_addr) + { + printf ("incorrect mop addr: %p/%p line=%d\n", + addr, mop_addr, mop_line); + exit (1); + } + + pc = (unsigned long long)__builtin_return_address(0); + if (pc < mop_pc - 100 || pc > mop_pc + 100) + { + printf ("incorrect mop pc: %p/%p line=%d\n", + (void*)pc, (void*)mop_pc, mop_line); + exit (1); + } + + depth = __tsan_shadow_stack - shadow_stack - 1; + if (depth != mop_depth) + { + printf ("incorrect mop depth: %d/%d line=%d\n", + depth, mop_depth, mop_line); + exit (1); + } + + if (flags != mop_flags) + { + printf ("incorrect mop flags: %x/%x line=%d\n", + flags, mop_flags, mop_line); + exit (1); + } + + mop_expect = 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan.h ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-ignore.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) @@ -0,0 +1,49 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan -O1 -ftsan-ignore=tsan-ignore.ignore" } */ +#include "tsan.h" +#include "tsan-ignore.h" + +/* Check ignore file handling. */ + +int +foo (int *p) +{ + p [0] = 1; +} + +int bar (int *p) +{ + p [0] = 1; +} + +int baz (int *p) +{ + p [0] = 1; +} + +int bla (int *p) +{ + p [0] = 1; +} + +int xxx (int *p) +{ + p [0] = 1; +} + +int +main (void) +{ + int p, x; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + /* All these functions must be ignored. */ + foo (&x); + bar (&x); + baz (&x); + bla (&x); + xxx (&x); + in_tsan_ignore_header (&x); + p = 0; + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-ignore.h =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) @@ -0,0 +1,5 @@ +int +in_tsan_ignore_header (int *p) +{ + p [0] = 1; +} Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.h ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-stack.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) @@ -0,0 +1,34 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan -O1" } */ +#include "tsan.h" + +/* Check shadow stack maintance. */ + +void barbaz (int volatile *p) +{ + __tsan_expect_mop(2, p, 1, 1, sizeof(*p), __LINE__); + *p = 11; +} + +void +foobar (int volatile *p) +{ + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + p[0] = 13; + barbaz (p); + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + p[0] = 17; +} + +int +main (void) +{ + int volatile p; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + p = 0; + foobar (&p); + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + p = 1; + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-stack.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-mop.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) @@ -0,0 +1,93 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan -O1" } */ +#include "tsan.h" + +/* Sanity check for memory accesses instrumentation. */ + +typedef struct C C; +struct C +{ + int r, g, b; +}; + +typedef struct P P; +struct P +{ + int x, y; + C c; + C *cp; + C ca[2]; +}; + +void teststructp (P *p) +{ + int volatile tmp; + + __tsan_expect_mop(2, &p->x, 1, 1, sizeof(p->x), __LINE__); + p->x = 42; + + __tsan_expect_mop(2, &p->x, 0, 1, sizeof(p->x), __LINE__); + tmp = p->x; + +} + +void testfor (P *p) +{ + long long volatile tmp = 0; + long long *a = (long long *)p->ca; + int i; + for (i = 0; i < 2; i++) + { + __tsan_expect_mop(2, &a [i], 0, 1, sizeof(a [0]), __LINE__); + tmp += a [i]; + } +} + +void +teststruct (void) +{ + P p; + int volatile tmp; + + __tsan_expect_mop(1, &p.x, 1, 1, sizeof(p.x), __LINE__); + p.x = 42; + + __tsan_expect_mop(1, &p.x, 0, 1, sizeof(p.x), __LINE__); + tmp = p.x; + + __tsan_expect_mop(1, &p.cp, 1, 1, sizeof(p.cp), __LINE__); + p.cp = &p.c; + + __tsan_expect_mop(1, &p.c.r, 1, 1, sizeof(p.c.r), __LINE__); + p.c.r = 11; + + teststructp (&p); + testfor (&p); +} + +void +foobar (int *p) +{ + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + p[0] = 1; + + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + *p = 2; + + __tsan_expect_mop(1, (char*)p+3, 1, 1, 1, __LINE__); + *((char*)p+3) = 3; +} + +int +main (void) +{ + int p; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + p = 0; + foobar (&p); + + teststruct (); + + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-mop.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/common.opt =================================================================== --- gcc/common.opt (revision 180522) +++ gcc/common.opt (working copy) @@ -1547,6 +1547,14 @@ Common RejectNegative Report Var(flag_mudflap_ignore_reads) Ignore read operations when inserting mudflap instrumentation +ftsan +Common RejectNegative Report Var(flag_tsan) +Add ThreadSanitizer instrumentation + +ftsan-ignore= +Common RejectNegative Joined Var(flag_tsan_ignore) +-ftsan-ignore=filename ThreadSanitizer ignore file + fdce Common Var(flag_dce) Init(1) Optimization Use the RTL dead code elimination pass Index: gcc/Makefile.in =================================================================== --- gcc/Makefile.in (revision 180522) +++ gcc/Makefile.in (working copy) @@ -1494,6 +1494,7 @@ tree-streamer-out.o \ tree-tailcall.o \ tree-threadsafe-analyze.o \ + tree-tsan.o \ tree-vect-generic.o \ tree-vect-patterns.o \ tree-vect-data-refs.o \ @@ -2814,6 +2815,12 @@ $(C_TREE_H) $(C_COMMON_H) $(GIMPLE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) \ output.h langhooks.h tree-mudflap.h $(TM_H) coretypes.h \ $(GGC_H) gt-tree-mudflap.h $(TREE_PASS_H) $(DIAGNOSTIC_CORE_H) +tree-tsan.o : $(CONFIG_H) $(SYSTEM_H) $(TREE_H) $(TREE_INLINE_H) \ + $(GIMPLE_H) $(DIAGNOSTIC_H) langhooks.h \ + $(TM_H) coretypes.h $(TREE_DUMP_H) $(TREE_PASS_H) $(CGRAPH_H) $(GGC_H) \ + $(BASIC_BLOCK_H) $(FLAGS_H) $(FUNCTION_H) \ + $(TM_P_H) $(TREE_FLOW_H) $(DIAGNOSTIC_CORE_H) $(GIMPLE_H) tree-iterator.h \ + intl.h cfghooks.h output.h options.h c-family/c-common.h tree-pretty-print.o : tree-pretty-print.c $(CONFIG_H) $(SYSTEM_H) \ $(TREE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) $(TREE_FLOW_H) \ $(TM_H) coretypes.h tree-iterator.h $(SCEV_H) langhooks.h \ Index: gcc/passes.c =================================================================== --- gcc/passes.c (revision 180522) +++ gcc/passes.c (working copy) @@ -1344,6 +1344,7 @@ NEXT_PASS (pass_split_crit_edges); NEXT_PASS (pass_pre); NEXT_PASS (pass_sink_code); + NEXT_PASS (pass_tsan); NEXT_PASS (pass_tree_loop); { struct opt_pass **p = &pass_tree_loop.pass.sub; -- This patch is available for review at http://codereview.appspot.com/5303083
Sign in to reply to this message.
On 2011/11/01 16:55:01, davidxl wrote: > > gcc/tree-tsan.c:161: tree __attribute__((weak)) > >> Explain this. > >> > > > > Done. > > Is it OK to leave __attribute__((weak))? It does not look like a good > > solution, however it works and I am not sure how to fix it w/o the > > attribute. > > Do I actually need lookup_name ()? Normally the runtime variables and > > functions (e.g. __tsan_shadow_stack) are not declared in user program, > > however unit tests do declare them and then I just lookup them instead of > > building declarations manually. Is it OK to just build a second declaration > > w/o looking it up first? I guess normally there are no doubled declarations > > in the compiler. > > > > You don't need this function. I mentioned you can use varpool_node_for_asm > method to do the variable lookup in middle end. Done. Use varpool_node_for_asm () instead of lookup_name (). > >> http://codereview.appspot.com/**5303083/diff/1/gcc/tree-tsan.** > >> > c#newcode182<http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode182> > >> gcc/tree-tsan.c:182: > >> Better use varpool_get_node interface. > >> > >> http://codereview.appspot.com/**5303083/diff/1/gcc/tree-tsan.** > >> > c#newcode186<http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode186> > >> gcc/tree-tsan.c:186: TREE_STATIC (def) = 1; > >> Why mark TREE_STATIC (def) = 1? Should the variable be defined in tsan > >> library? > >> > > > > How to properly use varpool in this case? > > Initially I build them as: > > > > def = build_decl (UNKNOWN_LOCATION, VAR_DECL, > > get_identifier (RTL_STACK), > > build_pointer_type (ptr_type_node)); > > TREE_STATIC (def) = 1; > > TREE_PUBLIC (def) = 0; > > DECL_INITIAL (def) = NULL; > > DECL_TLS_MODEL (def) = decl_default_tls_model (def); > > DECL_ARTIFICIAL (def) = 1; > > varpool_finalize_decl (def); > > varpool_mark_needed_node (varpool_node (def)); > > > > Taken from tree-profile.c by your suggestion. However it does not seem to > > work for me - programs crash. > > Current code is taken from elsewhere and seem to work for me. > > What I need to build is: > > > > extern __thread int __tsan_thread_ignore; */ > > > > Yes, they are defined in tsan library - I just need declarations. > > > > > I meant varpool_node_for_asm method. This method's implementation is not > efficient, but it should be find as you only do this once per file. Done. > >> http://codereview.appspot.com/**5303083/diff/1/gcc/tree-tsan.** > >> > c#newcode459<http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode459> > >> gcc/tree-tsan.c:459: s = NULL; > >> MODIFY_EXPR? directly use gimple_build_assign. > >> > > > > I've figured out that a single tree can produce several gimples due to > > limitations on gimples (e.g. a function call may have argument calculations > > as separate gimples). So I've concluded that the safe way to do it is to > > build a full tree first and then convert it to gimple seq. Isn't it the > > right way? What is the idiomatic way to build statements? > > > > > > Before build gimple assignment, use force_gimple_operand first. Done.
Sign in to reply to this message.
On 2011/11/01 16:59:04, davidxl wrote: > that means some existing bugs get exposed. It is quite likely. > Your previous version > simply skipped the target mem refs. Hummm... how can non-handling of some expressions lead to crashes? I would expect that it just leads to non 100% memory access coverage... > You will need to debug the > problem a little more. What I have is a tree with no type. I have no idea why it happens. (gdb) print t->common.typed->type $48 = (tree) 0x0 (gdb) print t->common.typed $47 = {base = {code = SSA_NAME, side_effects_flag = 0, constant_flag = 0, addressable_flag = 0, volatile_flag = 0, readonly_flag = 0, unsigned_flag = 0, asm_written_flag = 0, nowarning_flag = 0, used_flag = 0, nothrow_flag = 1, static_flag = 0, public_flag = 0, private_flag = 0, protected_flag = 0, deprecated_flag = 0, saturating_flag = 0, default_def_flag = 0, lang_flag_0 = 0, lang_flag_1 = 0, lang_flag_2 = 0, lang_flag_3 = 0, lang_flag_4 = 0, lang_flag_5 = 0, lang_flag_6 = 0, visited = 0, packed_flag = 0, user_align = 0, nameless_flag = 0, expr_folded_flag = 0, spare = 0, address_space = 0}, type = 0x0} > > David > > On Tue, Nov 1, 2011 at 5:26 AM, <mailto:dvyukov@google.com> wrote: > > On 2011/10/31 06:08:34, davidxl wrote: > >> > >> http://codereview.appspot.com/5303083/diff/1/gcc/passes.c#newcode1423 > >> gcc/passes.c:1423: NEXT_PASS (pass_tsan); > >> Move this to the same place as asan. Otherwise TARGET_MEM_REF won't be > > > > handled. > > > > After I moved the pass it started crashing: > > Program received signal SIGSEGV, Segmentation fault. > > 0x0000000000718f94 in is_gimple_reg_type (t=0x7ffff771efa0) > > at gimple.c:2960 > > 2960 return !AGGREGATE_TYPE_P (type); > > (gdb) bt > > #0 0x0000000000718f94 in is_gimple_reg_type (t=0x7ffff771efa0) > > at gimple.c:2960 > > #1 is_gimple_val (t=0x7ffff771efa0) at gimple.c:3028 > > #2 0x00000000008a5d20 in verify_types_in_gimple_reference > > (expr=0x7ffff76b74c0, require_lvalue=false) > > at tree-cfg.c:2934 > > #3 0x00000000008b2d4f in verify_gimple_in_cfg (fn=0x7ffff77c67e0) > > at tree-cfg.c:4382 > > #4 0x0000000000a061d6 in verify_ssa (check_modified_stmt=true) > > at tree-ssa.c:924 > > #5 0x00000000007f755c in execute_function_todo (data=Unhandled dwarf > > expression opcode 0xf3 > > ) > > at passes.c:1727 > > #6 0x00000000007f7e4d in execute_todo (flags=34854) at passes.c:1758 > > #7 0x00000000007fafda in execute_one_pass (pass=0x122a900) > > at passes.c:2104 > > > > The code seems to be (however I am not 100% sure): > > D.3617_33 = MEM[(const uint64_t *)ctx_11(D)].nhkey[D.3612_26]{lb: > > D.3810_279 sz: 8}; > > > > > > http://codereview.appspot.com/5303083/ > >
Sign in to reply to this message.
On 2011/11/01 17:20:11, davidxl wrote: > http://codereview.appspot.com/5303083/diff/3002/gcc/tree-tsan.c > File gcc/tree-tsan.c (right): > > http://codereview.appspot.com/5303083/diff/3002/gcc/tree-tsan.c#newcode1075 > gcc/tree-tsan.c:1075: for (eidx = 0; VEC_iterate (edge, exit_bb->preds, eidx, > e); eidx++) > Use FOR_EACH_EDGE macro Done. > http://codereview.appspot.com/5303083/diff/3002/gcc/tree-tsan.c#newcode1082 > gcc/tree-tsan.c:1082: gsi_insert_seq_before (&gsi, post_func_seq, > GSI_SAME_STMT); > On 2011/11/01 11:39:49, dvyukov wrote: > > Do I need to make a copy of POST_FUNC_SEQ here? > > I think that I do not need location info for this code at all, so is it OK to > > leave the seq w/o location and then insert it into several basic blocks? > > Yes, do not share gimple stmts. Done.
Sign in to reply to this message.
On 2011/11/01 18:05:46, davidxl wrote: > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c > File gcc/tree-tsan.c (right): > > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode432 > gcc/tree-tsan.c:432: /* Builds either (__tsan_shadow_stack += 1) or > (__tsan_shadow_stack -= 1) expression > Line too long. > > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode464 > gcc/tree-tsan.c:464: /* Builds either (__tsan_thread_ignore += 1) or > (__tsan_thread_ignore -= 1) expression > Long line. > > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode625 > gcc/tree-tsan.c:625: tree comp = expr->exp.operands [0]; > Use TREE_OPERAND > > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode627 > gcc/tree-tsan.c:627: comp = comp->exp.operands [0]; > Use TREE_OPERAND > > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode663 > gcc/tree-tsan.c:663: tree ref_target = expr->exp.operands [0]; > TREE_OPERAND Done. > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode623 > gcc/tree-tsan.c:623: && strcmp (decl_name (cfun->decl), "__base_dtor ") == 0) > How about inline instance of dtor? We always use -fno-inline. However, now I just removed "__base_dtor ". > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode631 > gcc/tree-tsan.c:631: if (TREE_CODE (comp) == SSA_NAME) > All the above can be simplified using get_base_addr Done. > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode638 > gcc/tree-tsan.c:638: "_vptr.", sizeof ("_vptr.") - 1) == 0) > This is a very hacky way of recognizing vptr field. C++ FE provides TYPE_VFIELD > macro to get the vptr field, but you will need to add a new langhook for it -- > which is not liked in upstream -- so the hacky way may be ok (as it is for error > checking purpose). I switched to DECL_VIRTUAL_P. > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode646 > gcc/tree-tsan.c:646: /* Checks as to whether expr refers to a read from vtlb. > vtlb -- vtbl. Done. > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode666 > gcc/tree-tsan.c:666: gimple ref_stmt = ref_target->ssa_name.def_stmt; > Use SSA_NAME_DEF_STMT > > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode685 > gcc/tree-tsan.c:685: } > Please refactor the is_dtor_vtpr_store code above for a new utility function -- > is_vptr_access. For now I just removed is_dtor_vptr_store (). It become broken after pass move or some other change anyway. > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode900 > gcc/tree-tsan.c:900: { > Remove {} Done. > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode911 > gcc/tree-tsan.c:911: bbd->has_sb = 0; > Where is the code that instrument function calls? I do not instrument calls. Instead I instrument func entry/exit.
Sign in to reply to this message.
On 2011/11/01 19:26:50, mjambor_suse.cz wrote: > Hi, > > sorry that I'm not using the fancy web tool but I do not want to use > my google account and gmail address in particular for work-related > stuff. > > On Tue, Nov 01, 2011 at 06:05:46PM +0000, mailto:davidxl@google.com wrote: > > > > ... > > > > > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode638 > > gcc/tree-tsan.c:638: "_vptr.", sizeof ("_vptr.") - 1) == 0) > > This is a very hacky way of recognizing vptr field. C++ FE provides > > TYPE_VFIELD macro to get the vptr field, but you will need to add a new > > langhook for it -- which is not liked in upstream -- so the hacky way > > may be ok (as it is for error checking purpose). > > > > If you have a FIELD_DECL and want to check whether it is a VPTR, you > can simply use DECL_VIRTUAL_P. Thank you! The code become much cleaner.
Sign in to reply to this message.
> http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode911 >> >> gcc/tree-tsan.c:911: bbd->has_sb = 0; >> Where is the code that instrument function calls? > > I do not instrument calls. Instead I instrument func entry/exit. > How are calls to locking and synchronization functions tracked? David > > http://codereview.appspot.com/5303083/ >
Sign in to reply to this message.
On Thu, Nov 3, 2011 at 6:31 AM, <dvyukov@google.com> wrote: > On 2011/11/01 16:59:04, davidxl wrote: >> >> that means some existing bugs get exposed. > > It is quite likely. > >> Your previous version >> simply skipped the target mem refs. > > Hummm... how can non-handling of some expressions lead to crashes? I > would expect that it just leads to non 100% memory access coverage... Likely the way the memop is instrumented is buggy. > >> You will need to debug the >> problem a little more. > > What I have is a tree with no type. I have no idea why it happens. > > (gdb) print t->common.typed->type > $48 = (tree) 0x0 > (gdb) print t->common.typed > $47 = {base = {code = SSA_NAME, side_effects_flag = 0, constant_flag = > 0, addressable_flag = 0, volatile_flag = 0, readonly_flag = 0, > unsigned_flag = 0, asm_written_flag = 0, nowarning_flag = 0, used_flag = > 0, nothrow_flag = 1, > static_flag = 0, public_flag = 0, private_flag = 0, protected_flag = > 0, deprecated_flag = 0, saturating_flag = 0, default_def_flag = 0, > lang_flag_0 = 0, lang_flag_1 = 0, lang_flag_2 = 0, lang_flag_3 = 0, > lang_flag_4 = 0, > lang_flag_5 = 0, lang_flag_6 = 0, visited = 0, packed_flag = 0, > user_align = 0, nameless_flag = 0, expr_folded_flag = 0, spare = 0, > address_space = 0}, type = 0x0} For debugging purpose, you should use debug print functions in tree-print.c and tree-pretty-print.c such as debug_tree, debug_generic_expr, debug_generic_stmt etc to dump trees instead of using raw gdb print. Here you have a SSA name without type -- it may be created by the tsan instrumentation code. David > > > > >> David > >> On Tue, Nov 1, 2011 at 5:26 AM, <mailto:dvyukov@google.com> wrote: >> > On 2011/10/31 06:08:34, davidxl wrote: >> >> >> >> > > http://codereview.appspot.com/5303083/diff/1/gcc/passes.c#newcode1423 >> >> >> gcc/passes.c:1423: NEXT_PASS (pass_tsan); >> >> Move this to the same place as asan. Otherwise TARGET_MEM_REF won't > > be >> >> > >> > handled. >> > >> > After I moved the pass it started crashing: >> > Program received signal SIGSEGV, Segmentation fault. >> > 0x0000000000718f94 in is_gimple_reg_type (t=0x7ffff771efa0) >> > at gimple.c:2960 >> > 2960 return !AGGREGATE_TYPE_P (type); >> > (gdb) bt >> > #0 0x0000000000718f94 in is_gimple_reg_type (t=0x7ffff771efa0) >> > at gimple.c:2960 >> > #1 is_gimple_val (t=0x7ffff771efa0) at gimple.c:3028 >> > #2 0x00000000008a5d20 in verify_types_in_gimple_reference >> > (expr=0x7ffff76b74c0, require_lvalue=false) >> > at tree-cfg.c:2934 >> > #3 0x00000000008b2d4f in verify_gimple_in_cfg > > (fn=0x7ffff77c67e0) >> >> > at tree-cfg.c:4382 >> > #4 0x0000000000a061d6 in verify_ssa (check_modified_stmt=true) >> > at tree-ssa.c:924 >> > #5 0x00000000007f755c in execute_function_todo (data=Unhandled > > dwarf >> >> > expression opcode 0xf3 >> > ) >> > at passes.c:1727 >> > #6 0x00000000007f7e4d in execute_todo (flags=34854) at > > passes.c:1758 >> >> > #7 0x00000000007fafda in execute_one_pass (pass=0x122a900) >> > at passes.c:2104 >> > >> > The code seems to be (however I am not 100% sure): >> > D.3617_33 = MEM[(const uint64_t > > *)ctx_11(D)].nhkey[D.3612_26]{lb: >> >> > D.3810_279 sz: 8}; >> > >> > >> > http://codereview.appspot.com/5303083/ >> > > > > > http://codereview.appspot.com/5303083/ >
Sign in to reply to this message.
On 2011/11/04 05:38:33, davidxl wrote: > > http://codereview.appspot.com/5303083/diff/1/gcc/tree-tsan.c#newcode911 > >> > >> gcc/tree-tsan.c:911: bbd->has_sb = 0; > >> Where is the code that instrument function calls? > > > > I do not instrument calls. Instead I instrument func entry/exit. > > > > How are calls to locking and synchronization functions tracked? They are intercepted by the runtime library. For static binaries they are intercepted by means of ld --wrap, for dynamic binaries we define functions with the same names and then use dlsym(RTLD_NEXT) to get original symbols.
Sign in to reply to this message.
On 2011/11/04 05:48:24, davidxl wrote: > On Thu, Nov 3, 2011 at 6:31 AM, <mailto:dvyukov@google.com> wrote: > > On 2011/11/01 16:59:04, davidxl wrote: > >> > >> that means some existing bugs get exposed. > > > > It is quite likely. > > > >> Your previous version > >> simply skipped the target mem refs. > > > > Hummm... how can non-handling of some expressions lead to crashes? I > > would expect that it just leads to non 100% memory access coverage... > > Likely the way the memop is instrumented is buggy. > > > > >> You will need to debug the > >> problem a little more. > > > > What I have is a tree with no type. I have no idea why it happens. > > > > (gdb) print t->common.typed->type > > $48 = (tree) 0x0 > > (gdb) print t->common.typed > > $47 = {base = {code = SSA_NAME, side_effects_flag = 0, constant_flag = > > 0, addressable_flag = 0, volatile_flag = 0, readonly_flag = 0, > > unsigned_flag = 0, asm_written_flag = 0, nowarning_flag = 0, used_flag = > > 0, nothrow_flag = 1, > > static_flag = 0, public_flag = 0, private_flag = 0, protected_flag = > > 0, deprecated_flag = 0, saturating_flag = 0, default_def_flag = 0, > > lang_flag_0 = 0, lang_flag_1 = 0, lang_flag_2 = 0, lang_flag_3 = 0, > > lang_flag_4 = 0, > > lang_flag_5 = 0, lang_flag_6 = 0, visited = 0, packed_flag = 0, > > user_align = 0, nameless_flag = 0, expr_folded_flag = 0, spare = 0, > > address_space = 0}, type = 0x0} > > For debugging purpose, you should use debug print functions in > tree-print.c and tree-pretty-print.c such as debug_tree, > debug_generic_expr, debug_generic_stmt etc to dump trees instead of > using raw gdb print. Here you have a SSA name without type -- it may > be created by the tsan instrumentation code. Thanks! I will try to dump the tree after tsan pass.
Sign in to reply to this message.
Fix crashes during instrumentation. The patch is for google/main branch. 2011-11-08 Dmitriy Vyukov <dvyukov@google.com> * gcc/doc/invoke.texi: * gcc/tree-tsan.c (enum tsan_ignore_type): (struct bb_data): (struct mop_desc): (struct tsan_ignore_desc): (build_var_decl): (get_shadow_stack_decl): (get_thread_ignore_decl): (get_handle_mop_decl): (ignore_append): (ignore_match): (ignore_load): (tsan_ignore): (build_stack_op): (build_rec_ignore_op): (build_stack_assign): (instr_mop): (instr_vptr_store): (is_func_instrumentation_required): (build_func_entry_instr): (build_func_exit_instr): (set_location): (is_vptr_store): (is_load_of_const): (handle_expr): (handle_gimple): (instrument_bblock): (instrument_mops): (instrument_func_entry): (instrument_func_exit): (tsan_pass): (tsan_gate): * gcc/tree-pass.h: * gcc/testsuite/gcc.dg/tsan-ignore.ignore: * gcc/testsuite/gcc.dg/tsan.h (__tsan_init): (__tsan_expect_mop): (__tsan_handle_mop): * gcc/testsuite/gcc.dg/tsan-ignore.c (foo): (int bar): (int baz): (int bla): (int xxx): (main): * gcc/testsuite/gcc.dg/tsan-ignore.h (in_tsan_ignore_header): * gcc/testsuite/gcc.dg/tsan-stack.c (void barbaz): (foobar): * gcc/testsuite/gcc.dg/tsan-mop.c (struct C): (struct P): (void teststructp): (void testfor): (teststruct): * gcc/common.opt: * gcc/Makefile.in: * gcc/passes.c: Index: gcc/doc/invoke.texi =================================================================== --- gcc/doc/invoke.texi (revision 180522) +++ gcc/doc/invoke.texi (working copy) @@ -308,6 +308,7 @@ -fdump-tree-ssa@r{[}-@var{n}@r{]} -fdump-tree-pre@r{[}-@var{n}@r{]} @gol -fdump-tree-ccp@r{[}-@var{n}@r{]} -fdump-tree-dce@r{[}-@var{n}@r{]} @gol -fdump-tree-gimple@r{[}-raw@r{]} -fdump-tree-mudflap@r{[}-@var{n}@r{]} @gol +-fdump-tree-tsan@r{[}-@var{n}@r{]} @gol -fdump-tree-dom@r{[}-@var{n}@r{]} @gol -fdump-tree-dse@r{[}-@var{n}@r{]} @gol -fdump-tree-phiprop@r{[}-@var{n}@r{]} @gol @@ -381,8 +382,8 @@ -floop-parallelize-all -flto -flto-compression-level @gol -flto-partition=@var{alg} -flto-report -fmerge-all-constants @gol -fmerge-constants -fmodulo-sched -fmodulo-sched-allow-regmoves @gol --fmove-loop-invariants fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol --fno-default-inline @gol +-fmove-loop-invariants -fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol +-ftsan -ftsan-ignore -fno-default-inline @gol -fno-defer-pop -fno-function-cse -fno-guess-branch-probability @gol -fno-inline -fno-math-errno -fno-peephole -fno-peephole2 @gol -fno-sched-interblock -fno-sched-spec -fno-signed-zeros @gol @@ -5896,6 +5897,11 @@ Dump each function after adding mudflap instrumentation. The file name is made by appending @file{.mudflap} to the source file name. +@item tsan +@opindex fdump-tree-tsan +Dump each function after adding ThreadSanitizer instrumentation. The file name is +made by appending @file{.tsan} to the source file name. + @item sra @opindex fdump-tree-sra Dump each function after performing scalar replacement of aggregates. The @@ -6674,6 +6680,12 @@ some protection against outright memory corrupting writes, but allows erroneously read data to propagate within a program. +@item -ftsan -ftsan-ignore +@opindex ftsan +@opindex ftsan-ignore +Add ThreadSanitizer instrumentation. Use @option{-ftsan-ignore} to specify +an ignore file. Refer to http://go/tsan for details. + @item -fthread-jumps @opindex fthread-jumps Perform optimizations where we check to see if a jump branches to a Index: gcc/tree-tsan.c =================================================================== --- gcc/tree-tsan.c (revision 0) +++ gcc/tree-tsan.c (revision 0) @@ -0,0 +1,1073 @@ +/* ThreadSanitizer instrumentation pass. + http://code.google.com/p/data-race-test + Copyright (C) 2011 + Free Software Foundation, Inc. + Contributed by Dmitry Vyukov <dvyukov@google.com> + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "intl.h" +#include "tm.h" +#include "basic-block.h" +#include "gimple.h" +#include "function.h" +#include "tree-flow.h" +#include "tree-pass.h" +#include "cfghooks.h" +#include "langhooks.h" +#include "output.h" +#include "options.h" +#include "target.h" +#include "cgraph.h" +#include "diagnostic.h" +#include <stdlib.h> +#include <stdio.h> + +/* ThreadSanitizer is a data race detector for C/C++ programs. + http://code.google.com/p/data-race-test/wiki/ThreadSanitizer + + The tool consists of two parts: + instrumentation module (this file) and a run-time library. + The instrumentation module maintains shadow call stacks + and intercepts interesting memory accesses. + The instrumentation is enabled with -ftsan flag. + + Instrumentation for shadow stack maintenance is as follows: + void somefunc () + { + __tsan_shadow_stack [-1] = __builtin_return_address (0); + __tsan_shadow_stack++; + // function body + __tsan_shadow_stack--; + } + + Interception for memory access interception is as follows: + *addr = 1; + __tsan_handle_mop (addr, flags); + where flags are (is_sblock | (is_store << 1) | ((sizeof (*addr) - 1) << 2). + is_sblock is used merely for optimization purposes and can always + be set to 1, see comments in instrument_mops function. + + Ignore files can be used to selectively non instrument some functions. + Ignore file is specified with -ftsan-ignore=filename flag. + There are 3 types of ignores: (1) do not instrument memory accesses + in the function, (2) do not create sblocks in the function + and (3) recursively ignore memory accesses in the function. + That last ignore type requires additional instrumentation of the form: + void somefunc () + { + __tsan_thread_ignore++; + // function body + __tsan_thread_ignore--; + } + + The run-time library provides __tsan_handle_mop function, + definitions of __tsan_shadow_stack and __tsan_thread_ignore variables, + and intercepts synchronization related functions. */ + +#define TSAN_IGNORE "__tsan_thread_ignore" +#define TSAN_STACK "__tsan_shadow_stack" +#define TSAN_MOP "__tsan_handle_mop" +#define TSAN_PERFIX "__tsan_" +#define MAX_MOP_BYTES 16 +#define SBLOCK_SIZE 5 + +enum tsan_ignore_type +{ + tsan_ignore_none = 1 << 0, /* Do not ignore. */ + tsan_ignore_func = 1 << 1, /* Completely ignore the whole func. */ + tsan_ignore_mop = 1 << 2, /* Do not instrument accesses. */ + tsan_ignore_rec = 1 << 3, /* Do not instrument accesses recursively. */ + tsan_ignore_hist = 1 << 4 /* Do not create superblocks. */ +}; + +/* Info associated with each basic block. + Used to determine super-blocks (see instrument_mops ()). */ + +struct bb_data +{ + int is_visited; + int has_sb; + const char *sb_file; + int sb_line_min; + int sb_line_max; +}; + +/* Memory access descriptor. */ + +struct mop_desc +{ + int is_call; + int is_store; + gimple_stmt_iterator gsi; + tree expr; +}; + +/* Descriptor of an ignore file entry. */ + +struct tsan_ignore_desc +{ + struct tsan_ignore_desc *next; + enum tsan_ignore_type type; + char *name; +}; + +/* Number of instrumented memory accesses in the current function. */ + +static int func_mops; + +/* Number of function calls in the current function. */ + +static int func_calls; + +/* Ignore status for the current function (see tsan_ignore_type). */ + +static enum tsan_ignore_type func_ignore; + +static int ignore_init = 0; +static struct tsan_ignore_desc *ignore_head; + +typedef struct mop_desc mop_desc; +DEF_VEC_O (mop_desc); +DEF_VEC_ALLOC_O (mop_desc, heap); +static VEC (mop_desc, heap) *mop_list; + +/* Returns a definition of a runtime variable with type TYP and name NAME. */ + +static tree +build_var_decl (tree typ, const char *name) +{ + tree id; + tree decl; + varpool_node_ptr var; + + /* Check if a user has defined it for testing. */ + id = get_identifier (name); + var = varpool_node_for_asm (id); + if (var != NULL) + { + decl = var->decl; + gcc_assert (TREE_CODE (decl) == VAR_DECL); + return decl; + } + + decl = build_decl (UNKNOWN_LOCATION, VAR_DECL, id, typ); + TREE_STATIC (decl) = 1; + TREE_PUBLIC (decl) = 1; + DECL_EXTERNAL (decl) = 1; + if (targetm.have_tls) + DECL_TLS_MODEL (decl) = decl_default_tls_model (decl); + TREE_USED (decl) = 1; + TREE_THIS_VOLATILE (decl) = 1; + SET_DECL_ASSEMBLER_NAME (decl, id); + return decl; +} + +/* Builds the following decl + extern __thread void **__tsan_shadow_stack; */ + +static tree +get_shadow_stack_decl (void) +{ + static tree decl; + + if (decl == NULL) + decl = build_var_decl (build_pointer_type (ptr_type_node), TSAN_STACK); + return decl; +} + +/* Builds the following decl + extern __thread int __tsan_thread_ignore; */ + +static tree +get_thread_ignore_decl (void) +{ + static tree decl; + + if (decl == NULL) + decl = build_var_decl (integer_type_node, TSAN_IGNORE); + return decl; +} + +/* Builds the following decl + void __tsan_handle_mop (void *addr, unsigned flags); */ + +static tree +get_handle_mop_decl (void) +{ + tree id; + tree fn_type; + varpool_node_ptr var; + static tree decl; + + if (decl != NULL) + return decl; + + /* Check if a user has defined it for testing. */ + id = get_identifier (TSAN_MOP); + var = varpool_node_for_asm (id); + if (var != NULL) + { + decl = var->decl; + gcc_assert (TREE_CODE (decl) == FUNCTION_DECL); + return decl; + } + + fn_type = build_function_type_list (void_type_node, ptr_type_node, + integer_type_node , NULL_TREE); + decl = build_fn_decl (TSAN_MOP, fn_type); + TREE_NOTHROW (decl) = 1; + DECL_ATTRIBUTES (decl) = tree_cons (get_identifier ("leaf"), + NULL, DECL_ATTRIBUTES (decl)); + DECL_ASSEMBLER_NAME (decl); + return decl; +} + +/* Adds new ignore definition to the global list. + TYPE is the ignore type (see tsan_ignore_type). + NAME is the ignore pattern (e.g. "std*string*insert"). */ + +static void +ignore_append (enum tsan_ignore_type type, char *name) +{ + struct tsan_ignore_desc *desc; + + desc = XCNEW (struct tsan_ignore_desc); + desc->type = type; + desc->name = xstrdup (name); + desc->next = ignore_head; + ignore_head = desc; +} + +/* Checks as to whether identifier STR matches template TEMPL. + Templates can only contain '*', e.g. 'std*string*insert'. + Templates implicitly start and end with '*' + since they are matched against mangled names. + Returns non-zero if STR is matched against TEMPL. */ + +static int +ignore_match (char *templ, const char *str) +{ + char *tpos; + const char *spos; + + while (templ && templ [0]) + { + if (templ [0] == '*') + { + templ++; + continue; + } + if (str [0] == 0) + return 0; + tpos = strchr (templ, '*'); + if (tpos != NULL) + tpos [0] = 0; + spos = strstr (str, templ); + str = spos + strlen (templ); + templ = tpos; + if (tpos != NULL) + tpos [0] = '*'; + if (spos == NULL) + return 0; + } + return 1; +} + +/* Loads ignore definitions from the file specified by -ftsan-ignore=filename. + The result is stored in the global ignore_head list. + Ignore files have the following format: + +# This is a comment - ignored + +# The below line says to not instrument memory accesses +# in all functions that match 'std*string*insert' +fun:std*string*insert + +# The below line says to not instrument memory accesses +# in the function called 'foobar' *and* in all functions +# that it calls recursively +fun_r:foobar + +# The below line says to not create superblocks +# in the function called 'barbaz' +fun_hist:barbaz + +# Ignore all functions in the source file +src:atomic.c + +# Everything else is uninteresting for us (e.g. obj:) +*/ + +static void +ignore_load (void) +{ + FILE *f; + char *line; + size_t linesz; + ssize_t sz; + char buf [PATH_MAX]; + + if(getenv("GCCTSAN_PAUSE")) + { + int res; + printf("ATTACH A DEBUGGER AND PRESS ENTER\n"); + res = scanf("%s", buf); + (void)res; + } + + if (flag_tsan_ignore == NULL || flag_tsan_ignore [0] == 0) + return; + + f = fopen (flag_tsan_ignore, "r"); + if (f == NULL) + { + /* Try to open it relative to main_input_filename. */ + strncpy (buf, main_input_filename, sizeof (buf)); + buf [sizeof (buf) - 1] = 0; + line = strrchr (buf, '/'); + if (line != NULL) + { + line++; + strncpy (line, flag_tsan_ignore, sizeof (buf) - (line - buf)); + buf [sizeof (buf) - 1] = 0; + f = fopen (buf, "r"); + } + } + if (f == NULL) + { + error ("failed to open ignore file '%s'\n", flag_tsan_ignore); + return; + } + + line = 0; + linesz = 0; + while ((sz = getline (&line, &linesz, f)) != -1) + { + if (sz == 0) + continue; + /* Strip line terminator. */ + if (line [sz - 1] == '\r' || line [sz - 1] == '\n') + line [sz - 1] = 0; + if (strncmp (line, "src:", sizeof ("src:") - 1) == 0) + ignore_append (tsan_ignore_func, line + sizeof ("src:") - 1); + else if (strncmp (line, "fun:", sizeof ("fun:") - 1) == 0) + ignore_append (tsan_ignore_mop, line + sizeof ("fun:") - 1); + else if (strncmp (line, "fun_r:", sizeof ("fun_r:") - 1) == 0) + ignore_append (tsan_ignore_rec, line + sizeof ("fun_r:") - 1); + else if (strncmp (line, "fun_hist:", sizeof ("fun_hist:") - 1) == 0) + ignore_append (tsan_ignore_hist, line + sizeof ("fun_hist:") - 1); + /* Other lines are not interesting. */ + } + + free (line); + fclose (f); +} + +/* Returns ignore status for the current function. */ + +static enum tsan_ignore_type +tsan_ignore (void) +{ + const char *func_name; + const char *src_name; + struct tsan_ignore_desc *desc; + + if (ignore_init == 0) + { + ignore_load (); + ignore_init = 1; + } + + src_name = expand_location (cfun->function_start_locus).file; + if (src_name == NULL) + src_name = ""; + + func_name = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (cfun->decl)); + /* Ignore all functions starting with __tsan_ - intended for testing. */ + if (strncmp (func_name, TSAN_PERFIX, sizeof (TSAN_PERFIX) - 1) == 0) + return tsan_ignore_func; + + for (desc = ignore_head; desc; desc = desc->next) + { + if (desc->type == tsan_ignore_func) + { + if (ignore_match (desc->name, src_name)) + return desc->type; + } + else if (ignore_match (desc->name, func_name)) + return desc->type; + } + return tsan_ignore_none; +} + +/* Builds either (__tsan_shadow_stack += 1) or (__tsan_shadow_stack -= 1) + expression depending on DO_DEC parameter. Appends the result to SEQ. */ + +static void +build_stack_op (gimple_seq *seq, bool do_dec) +{ + tree op_size; + double_int op_size_cst; + unsigned long long size_val; + unsigned long long size_valhi; + tree op_expr; + gimple assign; + tree sstack_decl; + gimple_seq s; + + op_size = TYPE_SIZE (ptr_type_node); + op_size_cst = tree_to_double_int (op_size); + size_val = op_size_cst.low / BITS_PER_UNIT; + size_valhi = 0; + if (do_dec) + { + size_val = -size_val; + size_valhi = -1; + } + op_size = build_int_cst_wide (sizetype, size_val, size_valhi); + sstack_decl = get_shadow_stack_decl (); + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, sstack_decl, op_size); + + s = NULL; + op_expr = force_gimple_operand (op_expr, &s, true, NULL_TREE); + gimple_seq_add_seq (seq, s); + + assign = gimple_build_assign (sstack_decl, op_expr); + gimple_seq_add_stmt (seq, assign); +} + +/* Builds either (__tsan_thread_ignore += 1) or (__tsan_thread_ignore -= 1) + expression depending on OP parameter. Stores the result in SEQ. */ + +static void +build_rec_ignore_op (gimple_seq *seq, enum tree_code op) +{ + tree rec_expr; + gimple_seq rec_inc; + gimple rec_assign; + tree ignore_decl; + + ignore_decl = get_thread_ignore_decl (); + rec_expr = build2 (op, integer_type_node, ignore_decl, integer_one_node); + rec_inc = NULL; + rec_expr = force_gimple_operand (rec_expr, &rec_inc, true, NULL_TREE); + gimple_seq_add_seq (seq, rec_inc); +/* + rec_inc = NULL; + ignore_decl = force_gimple_operand (ignore_decl, &rec_inc, true, NULL_TREE); + gimple_seq_add_seq (seq, rec_inc); +*/ + rec_assign = gimple_build_assign (ignore_decl, rec_expr); + gimple_seq_add_stmt (seq, rec_assign); +} + +/* Build the following gimple sequence: + __tsan_shadow_stack [-1] = __builtin_return_address (0); + Stores the result in SEQ. */ + +static void +build_stack_assign (gimple_seq *seq) +{ + tree pc_addr; + tree op_size; + tree op_expr; + tree stack_op; + tree retaddr_decl; + tree assign; + + retaddr_decl = implicit_built_in_decls [BUILT_IN_RETURN_ADDRESS]; + pc_addr = build_call_expr (retaddr_decl, 1, integer_zero_node); + op_size = build_int_cst_wide (sizetype, -(POINTER_SIZE / BITS_PER_UNIT), -1); + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, + get_shadow_stack_decl (), op_size); + stack_op = build1 (INDIRECT_REF, ptr_type_node, op_expr); + assign = build2 (MODIFY_EXPR, ptr_type_node, stack_op, pc_addr); + force_gimple_operand (assign, seq, true, NULL_TREE); +} + +/* Builds the following gimple sequence: + __tsan_handle_mop (&EXPR, + (IS_SBLOCK | (IS_STORE << 1) | ((sizeof (EXPR) - 1) << 2); + The result is stored in GSEQ. */ + +static void +instr_mop (tree expr, int is_store, int is_sblock, gimple_seq *gseq) +{ + tree addr_expr; + tree expr_type; + unsigned size; + unsigned flags; + tree flags_expr; + tree call_expr; + + gcc_assert (gseq != 0 && *gseq == 0); + gcc_assert (is_gimple_addressable (expr)); + + addr_expr = build_addr (unshare_expr (expr), current_function_decl); + expr_type = TREE_TYPE (expr); + while (TREE_CODE (expr_type) == ARRAY_TYPE) + expr_type = TREE_TYPE (expr_type); + size = TREE_INT_CST_LOW (TYPE_SIZE (expr_type)); + size = size / BITS_PER_UNIT; + if (size > MAX_MOP_BYTES) + size = MAX_MOP_BYTES; + size -= 1; + flags = ((!!is_sblock << 0) + (!!is_store << 1) + (size << 2)); + flags_expr = build_int_cst (unsigned_type_node, flags); + call_expr = build_call_expr (get_handle_mop_decl (), + 2, addr_expr, flags_expr); + force_gimple_operand (call_expr, gseq, true, 0); +} + +/* Builds the following gimple sequence: + int is_store = (EXPR != RHS); // The temp is not actually introduced. + __tsan_handle_mop (&EXPR, + (IS_SBLOCK | (IS_STORE << 1) | ((sizeof (EXPR) - 1) << 2); + The result is stored in GSEQ. */ + +static void +instr_vptr_store (tree expr, tree rhs, int is_sblock, gimple_seq *gseq) +{ + tree expr_ptr; + tree addr_expr; + tree expr_type; + tree expr_size; + double_int size; + unsigned flags; + tree flags_expr; + gimple_seq flags_seq; + gimple collect; + tree is_store_expr; + + expr_ptr = build_addr (unshare_expr (expr), current_function_decl); + addr_expr = force_gimple_operand (expr_ptr, gseq, true, NULL_TREE); + expr_type = TREE_TYPE (expr); + while (TREE_CODE (expr_type) == ARRAY_TYPE) + expr_type = TREE_TYPE (expr_type); + expr_size = TYPE_SIZE (expr_type); + size = tree_to_double_int (expr_size); + gcc_assert (size.high == 0 && size.low != 0); + if (size.low > 128) + size.low = 128; + size.low = (size.low / 8) - 1; + flags = ((!!is_sblock << 0) + (size.low << 2)); + flags_expr = build_int_cst (unsigned_type_node, flags); + is_store_expr = build2 (NE_EXPR, unsigned_type_node, + build1 (VIEW_CONVERT_EXPR, size_type_node, expr), + build1 (VIEW_CONVERT_EXPR, size_type_node, rhs)); + is_store_expr = build2 (LSHIFT_EXPR, unsigned_type_node, + is_store_expr, integer_one_node); + flags_expr = build2 (BIT_IOR_EXPR, unsigned_type_node, + is_store_expr, flags_expr); + flags_seq = 0; + flags_expr = force_gimple_operand (flags_expr, &flags_seq, true, NULL_TREE); + gimple_seq_add_seq (gseq, flags_seq); + collect = gimple_build_call ( + get_handle_mop_decl (), 2, addr_expr, flags_expr); + gimple_seq_add_stmt (gseq, collect); +} + +/* Returns true if function entry and exit need to be instrumented. */ + +static bool +is_func_instrumentation_required (void) +{ + if (func_calls == 0 && func_mops == 0) + return false; + if (func_ignore != tsan_ignore_rec) + return true; + if (func_ignore == tsan_ignore_rec && func_calls != 0) + return true; + return false; +} + +/* Returns gimple seq that needs to be inserted at function entry. */ + +static gimple_seq +build_func_entry_instr (void) +{ + gimple_seq gs; + + gs = NULL; + gcc_assert (is_func_instrumentation_required ()); + if (func_ignore != tsan_ignore_rec) + { + build_stack_assign (&gs); + build_stack_op (&gs, false); + } + else + build_rec_ignore_op (&gs, PLUS_EXPR); + return gs; +} + +/* Returns gimple seq that needs to be inserted before function exit. */ + +static gimple_seq +build_func_exit_instr (void) +{ + gimple_seq gs; + + gs = NULL; + gcc_assert (is_func_instrumentation_required ()); + if (func_ignore != tsan_ignore_rec) + build_stack_op (&gs, true); + else + build_rec_ignore_op (&gs, MINUS_EXPR); + return gs; +} + +/* Sets location LOC for all gimples in the SEQ. */ + +static void +set_location (gimple_seq seq, location_t loc) +{ + gimple_seq_node n; + + for (n = gimple_seq_first (seq); n != NULL; n = n->next) + gimple_set_location (n->stmt, loc); + verify_gimple_in_seq (seq); +} + +/* Check as to whether EXPR refers to a store to vptr. */ + +static tree +is_vptr_store (gimple stmt, tree expr, int is_store) +{ + if (is_store == 1 + && gimple_assign_single_p (stmt) + && TREE_CODE (expr) == COMPONENT_REF) + { + tree field = TREE_OPERAND (expr, 1); + if (TREE_CODE (field) == FIELD_DECL + && DECL_VIRTUAL_P (field)) + return gimple_assign_rhs1 (stmt); + } + return NULL; +} + +/* Checks as to whether EXPR refers to constant var/field/param. + Don't bother to instrument them. */ + +static int +is_load_of_const (tree expr, int is_store) +{ + if (is_store) + return 0; + if (TREE_CODE (expr) == COMPONENT_REF) + expr = TREE_OPERAND (expr, 1); + if (TREE_CODE (expr) == VAR_DECL + || TREE_CODE (expr) == PARM_DECL + || TREE_CODE (expr) == FIELD_DECL) + { + if (TREE_READONLY (expr)) + return 1; + } + return 0; +} + +/* Checks as to whether EXPR needs to be instrumented, + if so puts it into the MOP_LIST. + GSI is the iterator from which EXPR was extracted. + IS_STORE says as to whether EXPR refers to a memory store + or a memory load. */ + +static void +handle_expr (gimple_stmt_iterator gsi, tree expr, int is_store, + VEC (mop_desc, heap) **mop_list) +{ + enum tree_code tcode; + struct mop_desc mop; + unsigned fld_off; + unsigned fld_size; + + tcode = TREE_CODE (expr); + + /* Below are things we do not instrument + (no possibility of races or not implemented yet). */ + if ((func_ignore & (tsan_ignore_mop | tsan_ignore_rec)) + || get_base_address (expr) == NULL + /* Compiler-emitted artificial variables. */ + || (DECL_P (expr) && DECL_ARTIFICIAL (expr)) + /* The var does not live in memory -> no possibility of races. */ + || (tcode == VAR_DECL + && TREE_ADDRESSABLE (expr) == 0 + && DECL_EXTERNAL (expr) == 0) + /* TODO (dvyukov): not implemented. */ + || TREE_CODE (TREE_TYPE (expr)) == RECORD_TYPE + /* TODO (dvyukov): not implemented. */ + || tcode == CONSTRUCTOR + /* TODO (dvyukov): not implemented. */ + || tcode == PARM_DECL + /* Load of a const variable/parameter/field. */ + || is_load_of_const (expr, is_store)) + return; + + if (tcode == COMPONENT_REF) + { + tree field = TREE_OPERAND (expr, 1); + if (TREE_CODE (field) == FIELD_DECL) + { + fld_off = TREE_INT_CST_LOW (DECL_FIELD_BIT_OFFSET (field)); + fld_size = TREE_INT_CST_LOW (DECL_SIZE (field)); + if (((fld_off % BITS_PER_UNIT) != 0) + || ((fld_size % BITS_PER_UNIT) != 0)) + { + /* As of now it crashes compilation. + TODO (dvyukov): handle bit-fields as if touching + the whole field. */ + return; + } + } + } + + /* TODO (dvyukov): handle other cases + (FIELD_DECL, MEM_REF, ARRAY_RANGE_REF, TARGET_MEM_REF, ADDR_EXPR). */ + if (tcode != ARRAY_REF + && tcode != VAR_DECL + && tcode != COMPONENT_REF + && tcode != INDIRECT_REF + && tcode != MEM_REF) + return; + + mop.is_call = 0; + mop.gsi = gsi; + mop.expr = expr; + mop.is_store = is_store; + VEC_safe_push (mop_desc, heap, *mop_list, &mop); +} + +/* Collects all interesting memory accesses from the gimple pointed to by GSI + into MOP_LIST. */ + +static void +handle_gimple (gimple_stmt_iterator gsi, VEC (mop_desc, heap) **mop_list) +{ + unsigned i; + struct mop_desc mop; + gimple stmt; + enum gimple_code gcode; + tree rhs; + tree lhs; + + stmt = gsi_stmt (gsi); + gcode = gimple_code (stmt); + if (gcode >= LAST_AND_UNUSED_GIMPLE_CODE) + return; + + switch (gcode) + { + case GIMPLE_CALL: + { + func_calls += 1; + memset (&mop, 0, sizeof (mop)); + mop.is_call = 1; + VEC_safe_push (mop_desc, heap, *mop_list, &mop); + break; + } + + case GIMPLE_ASSIGN: + { + /* Handle assignment lhs as store. */ + lhs = gimple_assign_lhs (stmt); + handle_expr (gsi, lhs, 1, mop_list); + + /* Handle operands as loads. */ + for (i = 1; i < gimple_num_ops (stmt); i++) + { + rhs = gimple_op (stmt, i); + handle_expr (gsi, rhs, 0, mop_list); + } + break; + } + + default: + break; + } +} + +/* Instruments single basic block BB. + BBD is the sblock info associated with the block. */ + +static void +instrument_bblock (struct bb_data *bbd, basic_block bb) +{ + int ix; + int is_sblock; + gimple_stmt_iterator gsi; + struct mop_desc *mop; + gimple stmt; + location_t loc; + expanded_location eloc; + gimple_seq instr_seq; + tree rhs; + + /* Iterate over all gimples and collect interesting mops into mop_list. */ + VEC_free (mop_desc, heap, mop_list); + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + handle_gimple (gsi, &mop_list); + } + + mop = 0; + for (ix = 0; VEC_iterate (mop_desc, mop_list, ix, mop); ix += 1) + { + if (mop->is_call != 0) + { + /* After a function call we must start a brand new sblock, + because the function can contain synchronization. */ + bbd->has_sb = 0; + continue; + } + + func_mops += 1; + stmt = gsi_stmt (mop->gsi); + loc = gimple_location (stmt); + eloc = expand_location (loc); + + /* Check as to whether we may not set sblock flag for the access. */ + is_sblock = (bbd->has_sb == 0 + || !(eloc.file != 0 + && bbd->sb_file != 0 + && strcmp (eloc.file, bbd->sb_file) == 0 + && eloc.line >= bbd->sb_line_min + && eloc.line <= bbd->sb_line_max)); + + if (func_ignore == tsan_ignore_hist) + is_sblock = 0; + + if (is_sblock) + { + /* Start new sblock with new source info. */ + bbd->has_sb = 1; + bbd->sb_file = eloc.file; + bbd->sb_line_min = eloc.line; + bbd->sb_line_max = eloc.line + SBLOCK_SIZE; + } + + instr_seq = 0; + rhs = is_vptr_store (stmt, mop->expr, mop->is_store); + if (rhs == NULL) + instr_mop (mop->expr, mop->is_store, is_sblock, &instr_seq); + else + instr_vptr_store (mop->expr, rhs, is_sblock, &instr_seq); + gcc_assert (instr_seq != 0); + set_location (instr_seq, loc); + /* Instrumentation for assignment of a function result + must be inserted after the call. Instrumentation for + reads of function arguments must be inserted before the call. + That's because the call can contain synchronization. */ + if (is_gimple_call (stmt) && mop->is_store == 1) + gsi_insert_seq_after (&mop->gsi, instr_seq, GSI_NEW_STMT); + else + gsi_insert_seq_before (&mop->gsi, instr_seq, GSI_SAME_STMT); + } +} + +/* Instruments all interesting memory accesses in the current function. */ + +static void +instrument_mops (void) +{ + basic_block bb; + int *blocks_inverted; + struct bb_data *bb_data; + struct bb_data *pred; + struct bb_data *bbd; + edge e; + edge_iterator ei; + int sb_line_min, sb_line_max; + int cnt, i; + + /* The function does basic block traversal in reverse top sort order + of the inverted CFG. Such order is required to properly mark super-blocks. + The idea behind super-blocks is as follows. + If several memory accesses happen within SBLOCK_SIZE source code lines + from each other, then we only mark the first access as SBLOCK. + This allows the runtime library to memorize a stack trace + only for the first access and do not memorize for others. + This significantly reduces memory consumption in exchange for slightly + imprecise stack traces for previous accesses. */ + + blocks_inverted = XNEWVEC (int, last_basic_block + NUM_FIXED_BLOCKS); + bb_data = XCNEWVEC (struct bb_data, last_basic_block + NUM_FIXED_BLOCKS); + cnt = inverted_post_order_compute (blocks_inverted); + for (i = 0; i < cnt; i++) + { + bb = BASIC_BLOCK (blocks_inverted [i]); + bbd = &bb_data [bb->index]; + /* Iterate over all predecessors and merge their sblock info. */ + FOR_EACH_EDGE (e, ei, bb->preds) + { + pred = &bb_data [e->src->index]; + if (!pred->is_visited || !pred->has_sb || pred == bbd) + { + /* If there is a not visited predecessor, + or a predecessor with no active sblock info, + or a self-loop, then we will have to start + a brand new sblock on next memory access. */ + bbd->has_sb = 0; + break; + } + else if (bbd->has_sb == 0) + { + /* If it's a first predecessor, just copy the info. */ + bbd->has_sb = 1; + bbd->sb_file = pred->sb_file; + bbd->sb_line_min = pred->sb_line_min; + bbd->sb_line_max = pred->sb_line_max; + } + else + { + /* Otherwise, find the interception + between two sblock descriptors. */ + bbd->has_sb = 0; + if (bbd->sb_file != 0 && pred->sb_file != 0 + && strcmp (bbd->sb_file, pred->sb_file) == 0) + { + sb_line_min = MAX (bbd->sb_line_min, pred->sb_line_min); + sb_line_max = MIN (bbd->sb_line_max, pred->sb_line_max); + if (sb_line_min <= sb_line_max) + { + bbd->has_sb = 1; + bbd->sb_line_min = sb_line_min; + bbd->sb_line_max = sb_line_max; + } + } + /* No interception, have to start new sblock. */ + if (bbd->has_sb == 0) + break; + } + } + + instrument_bblock (bbd, bb); + bbd->is_visited = 1; + } + + free (blocks_inverted); + free (bb_data); +} + +/* Instruments function entry. */ + +static void +instrument_func_entry (void) +{ + gimple_seq seq; + basic_block entry_bb; + edge entry_edge; + gimple_stmt_iterator gsi; + + /* Insert new BB before the first BB. */ + seq = build_func_entry_instr (); + gcc_assert (seq != NULL); + entry_bb = ENTRY_BLOCK_PTR; + entry_edge = single_succ_edge (entry_bb); + set_location (seq, cfun->function_start_locus); + entry_bb = split_edge (entry_edge); + gsi = gsi_start_bb (entry_bb); + gsi_insert_seq_after (&gsi, seq, GSI_NEW_STMT); +} + +/* Instruments function exits. */ + +static void +instrument_func_exit (void) +{ + location_t loc; + gimple_seq seq; + basic_block exit_bb; + gimple_stmt_iterator gsi; + gimple stmt; + edge e; + edge_iterator ei; + + /* Find all function exits. */ + exit_bb = EXIT_BLOCK_PTR; + FOR_EACH_EDGE (e, ei, exit_bb->preds) + { + gsi = gsi_last_bb (e->src); + stmt = gsi_stmt (gsi); + gcc_assert (gimple_code (stmt) == GIMPLE_RETURN); + loc = gimple_location (stmt); + seq = build_func_exit_instr (); + gcc_assert (seq != NULL); + set_location (seq, loc); + gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT); + } +} + +/* ThreadSanitizer instrumentation pass. */ + +static unsigned +tsan_pass (void) +{ + gimplify_ctx gctx; + + func_ignore = tsan_ignore (); + if (func_ignore == tsan_ignore_func) + return 0; + + func_calls = 0; + func_mops = 0; + + push_gimplify_context (&gctx); + + instrument_mops (); + + if (is_func_instrumentation_required ()) + { + instrument_func_entry (); + instrument_func_exit (); + } + + pop_gimplify_context (NULL); + + return 0; +} + +/* The pass's gate. */ + +static bool +tsan_gate (void) +{ + return flag_tsan != 0; +} + +/* The pass descriptor. */ + +struct gimple_opt_pass pass_tsan = {{ + GIMPLE_PASS, + "tsan", /* name */ + tsan_gate, /* gate */ + tsan_pass, /* execute */ + NULL, /* sub */ + NULL, /* next */ + 0, /* static_pass_number */ + TV_NONE, /* tv_id */ + PROP_trees | PROP_cfg, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + TODO_dump_cgraph | TODO_dump_func | TODO_verify_all + | TODO_update_ssa | TODO_update_address_taken /* todo_flags_finish */ +}}; + Property changes on: gcc/tree-tsan.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/tree-pass.h =================================================================== --- gcc/tree-pass.h (revision 180522) +++ gcc/tree-pass.h (working copy) @@ -352,6 +352,7 @@ extern struct gimple_opt_pass pass_mudflap_1; extern struct gimple_opt_pass pass_mudflap_2; +extern struct gimple_opt_pass pass_tsan; extern struct gimple_opt_pass pass_lower_cf; extern struct gimple_opt_pass pass_refactor_eh; extern struct gimple_opt_pass pass_lower_eh; Index: gcc/testsuite/gcc.dg/tsan-ignore.ignore =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) @@ -0,0 +1,7 @@ +#comment +fun:foo +fun:*bar +fun:baz* +fun:*bla* +fun:x*x +src:tsan-ignore.h Index: gcc/testsuite/gcc.dg/tsan.h =================================================================== --- gcc/testsuite/gcc.dg/tsan.h (revision 0) +++ gcc/testsuite/gcc.dg/tsan.h (revision 0) @@ -0,0 +1,101 @@ +/* Helper declarations and functions for ThreadSanitizer instrumentation (-ftsan) testing */ + +int printf (char *str, ...); +void exit (int); + +/* Variables referenced by the instrumentation. */ + +__thread void **__tsan_shadow_stack; +__thread int __tsan_thread_ignore; + +/* Local helper vars. */ + +__thread void *shadow_stack[1024]; +__thread int pad[1024]; +__thread int mop_expect = 0; +__thread int mop_depth = 0; +__thread void* mop_addr = 0; +__thread unsigned long long mop_pc = 0; +__thread unsigned mop_flags = 0; +__thread unsigned mop_line = 0; + +/* Setups shadow stack var (not instrumented). */ + +void __attribute__ ((constructor)) +__tsan_init (void) +{ + __tsan_shadow_stack = shadow_stack; +} + +/* Declare that we expect an instrumented memory access (not instrumented). + depth - stack depth of the mop (0 - main, 1 - func called from main and so on). + addr - memory access address. + is_store - store/load. + is_sblock - superblock flag of the access. + size - memory access size in bytes. */ + +void +__tsan_expect_mop (int depth, void const volatile *addr, int is_store, + int is_sblock, int size, unsigned line) +{ + if (mop_expect) + { + printf ("missed mop: addr=%p pc=%d line=%d\n", mop_addr, mop_pc, mop_line); + exit (1); + } + + mop_expect = 1; + mop_depth = depth; + mop_addr = (void*)addr; + mop_pc = (unsigned long long)__builtin_return_address(0); + mop_flags = !!is_sblock | (!!is_store << 1) | ((size - 1) << 2); + mop_line = line; +} + +/* Memory access function (referenced by instrumentation, not instrumented). */ + +void +__tsan_handle_mop (void *addr, unsigned flags) +{ + unsigned long long pc; + int depth; + + printf ("mop: addr=%p flags=%x called from %p line=%d\n", + addr, flags, __tsan_shadow_stack [-2], mop_line); + if (mop_expect == 0) + return; + + /* Verify parameters with what we expect. */ + + if (addr != mop_addr) + { + printf ("incorrect mop addr: %p/%p line=%d\n", + addr, mop_addr, mop_line); + exit (1); + } + + pc = (unsigned long long)__builtin_return_address(0); + if (pc < mop_pc - 100 || pc > mop_pc + 100) + { + printf ("incorrect mop pc: %p/%p line=%d\n", + (void*)pc, (void*)mop_pc, mop_line); + exit (1); + } + + depth = __tsan_shadow_stack - shadow_stack - 1; + if (depth != mop_depth) + { + printf ("incorrect mop depth: %d/%d line=%d\n", + depth, mop_depth, mop_line); + exit (1); + } + + if (flags != mop_flags) + { + printf ("incorrect mop flags: %x/%x line=%d\n", + flags, mop_flags, mop_line); + exit (1); + } + + mop_expect = 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan.h ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-ignore.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) @@ -0,0 +1,49 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan -O1 -ftsan-ignore=tsan-ignore.ignore" } */ +#include "tsan.h" +#include "tsan-ignore.h" + +/* Check ignore file handling. */ + +int +foo (int *p) +{ + p [0] = 1; +} + +int bar (int *p) +{ + p [0] = 1; +} + +int baz (int *p) +{ + p [0] = 1; +} + +int bla (int *p) +{ + p [0] = 1; +} + +int xxx (int *p) +{ + p [0] = 1; +} + +int +main (void) +{ + int p, x; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + /* All these functions must be ignored. */ + foo (&x); + bar (&x); + baz (&x); + bla (&x); + xxx (&x); + in_tsan_ignore_header (&x); + p = 0; + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-ignore.h =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) @@ -0,0 +1,5 @@ +int +in_tsan_ignore_header (int *p) +{ + p [0] = 1; +} Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.h ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-stack.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) @@ -0,0 +1,34 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan -O1" } */ +#include "tsan.h" + +/* Check shadow stack maintance. */ + +void barbaz (int volatile *p) +{ + __tsan_expect_mop(2, p, 1, 1, sizeof(*p), __LINE__); + *p = 11; +} + +void +foobar (int volatile *p) +{ + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + p[0] = 13; + barbaz (p); + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + p[0] = 17; +} + +int +main (void) +{ + int volatile p; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + p = 0; + foobar (&p); + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + p = 1; + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-stack.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-mop.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) @@ -0,0 +1,93 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan -O1" } */ +#include "tsan.h" + +/* Sanity check for memory accesses instrumentation. */ + +typedef struct C C; +struct C +{ + int r, g, b; +}; + +typedef struct P P; +struct P +{ + int x, y; + C c; + C *cp; + C ca[2]; +}; + +void teststructp (P *p) +{ + int volatile tmp; + + __tsan_expect_mop(2, &p->x, 1, 1, sizeof(p->x), __LINE__); + p->x = 42; + + __tsan_expect_mop(2, &p->x, 0, 1, sizeof(p->x), __LINE__); + tmp = p->x; + +} + +void testfor (P *p) +{ + long long volatile tmp = 0; + long long *a = (long long *)p->ca; + int i; + for (i = 0; i < 2; i++) + { + __tsan_expect_mop(2, &a [i], 0, 1, sizeof(a [0]), __LINE__); + tmp += a [i]; + } +} + +void +teststruct (void) +{ + P p; + int volatile tmp; + + __tsan_expect_mop(1, &p.x, 1, 1, sizeof(p.x), __LINE__); + p.x = 42; + + __tsan_expect_mop(1, &p.x, 0, 1, sizeof(p.x), __LINE__); + tmp = p.x; + + __tsan_expect_mop(1, &p.cp, 1, 1, sizeof(p.cp), __LINE__); + p.cp = &p.c; + + __tsan_expect_mop(1, &p.c.r, 1, 1, sizeof(p.c.r), __LINE__); + p.c.r = 11; + + teststructp (&p); + testfor (&p); +} + +void +foobar (int *p) +{ + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + p[0] = 1; + + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + *p = 2; + + __tsan_expect_mop(1, (char*)p+3, 1, 1, 1, __LINE__); + *((char*)p+3) = 3; +} + +int +main (void) +{ + int p; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + p = 0; + foobar (&p); + + teststruct (); + + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-mop.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/common.opt =================================================================== --- gcc/common.opt (revision 180522) +++ gcc/common.opt (working copy) @@ -1547,6 +1547,14 @@ Common RejectNegative Report Var(flag_mudflap_ignore_reads) Ignore read operations when inserting mudflap instrumentation +ftsan +Common RejectNegative Report Var(flag_tsan) +Add ThreadSanitizer instrumentation + +ftsan-ignore= +Common RejectNegative Joined Var(flag_tsan_ignore) +-ftsan-ignore=filename ThreadSanitizer ignore file + fdce Common Var(flag_dce) Init(1) Optimization Use the RTL dead code elimination pass Index: gcc/Makefile.in =================================================================== --- gcc/Makefile.in (revision 180522) +++ gcc/Makefile.in (working copy) @@ -1494,6 +1494,7 @@ tree-streamer-out.o \ tree-tailcall.o \ tree-threadsafe-analyze.o \ + tree-tsan.o \ tree-vect-generic.o \ tree-vect-patterns.o \ tree-vect-data-refs.o \ @@ -2814,6 +2815,12 @@ $(C_TREE_H) $(C_COMMON_H) $(GIMPLE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) \ output.h langhooks.h tree-mudflap.h $(TM_H) coretypes.h \ $(GGC_H) gt-tree-mudflap.h $(TREE_PASS_H) $(DIAGNOSTIC_CORE_H) +tree-tsan.o : $(CONFIG_H) $(SYSTEM_H) $(TREE_H) $(TREE_INLINE_H) \ + $(GIMPLE_H) $(DIAGNOSTIC_H) langhooks.h \ + $(TM_H) coretypes.h $(TREE_DUMP_H) $(TREE_PASS_H) $(CGRAPH_H) $(GGC_H) \ + $(BASIC_BLOCK_H) $(FLAGS_H) $(FUNCTION_H) \ + $(TM_P_H) $(TREE_FLOW_H) $(DIAGNOSTIC_CORE_H) $(GIMPLE_H) tree-iterator.h \ + intl.h cfghooks.h output.h options.h c-family/c-common.h tree-pretty-print.o : tree-pretty-print.c $(CONFIG_H) $(SYSTEM_H) \ $(TREE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) $(TREE_FLOW_H) \ $(TM_H) coretypes.h tree-iterator.h $(SCEV_H) langhooks.h \ Index: gcc/passes.c =================================================================== --- gcc/passes.c (revision 180522) +++ gcc/passes.c (working copy) @@ -1344,6 +1344,7 @@ NEXT_PASS (pass_split_crit_edges); NEXT_PASS (pass_pre); NEXT_PASS (pass_sink_code); + NEXT_PASS (pass_tsan); NEXT_PASS (pass_tree_loop); { struct opt_pass **p = &pass_tree_loop.pass.sub; -- This patch is available for review at http://codereview.appspot.com/5303083
Sign in to reply to this message.
Remove commented out code. The patch is for google/main branch. 2011-11-08 Dmitriy Vyukov <dvyukov@google.com> * gcc/doc/invoke.texi: * gcc/tree-tsan.c (enum tsan_ignore_type): (struct bb_data): (struct mop_desc): (struct tsan_ignore_desc): (build_var_decl): (get_shadow_stack_decl): (get_thread_ignore_decl): (get_handle_mop_decl): (ignore_append): (ignore_match): (ignore_load): (tsan_ignore): (build_stack_op): (build_rec_ignore_op): (build_stack_assign): (instr_mop): (instr_vptr_store): (is_func_instrumentation_required): (build_func_entry_instr): (build_func_exit_instr): (set_location): (is_vptr_store): (is_load_of_const): (handle_expr): (handle_gimple): (instrument_bblock): (instrument_mops): (instrument_func_entry): (instrument_func_exit): (tsan_pass): (tsan_gate): * gcc/tree-pass.h: * gcc/testsuite/gcc.dg/tsan-ignore.ignore: * gcc/testsuite/gcc.dg/tsan.h (__tsan_init): (__tsan_expect_mop): (__tsan_handle_mop): * gcc/testsuite/gcc.dg/tsan-ignore.c (foo): (int bar): (int baz): (int bla): (int xxx): (main): * gcc/testsuite/gcc.dg/tsan-ignore.h (in_tsan_ignore_header): * gcc/testsuite/gcc.dg/tsan-stack.c (void barbaz): (foobar): * gcc/testsuite/gcc.dg/tsan-mop.c (struct C): (struct P): (void teststructp): (void testfor): (teststruct): * gcc/common.opt: * gcc/Makefile.in: * gcc/passes.c: Index: gcc/doc/invoke.texi =================================================================== --- gcc/doc/invoke.texi (revision 180522) +++ gcc/doc/invoke.texi (working copy) @@ -308,6 +308,7 @@ -fdump-tree-ssa@r{[}-@var{n}@r{]} -fdump-tree-pre@r{[}-@var{n}@r{]} @gol -fdump-tree-ccp@r{[}-@var{n}@r{]} -fdump-tree-dce@r{[}-@var{n}@r{]} @gol -fdump-tree-gimple@r{[}-raw@r{]} -fdump-tree-mudflap@r{[}-@var{n}@r{]} @gol +-fdump-tree-tsan@r{[}-@var{n}@r{]} @gol -fdump-tree-dom@r{[}-@var{n}@r{]} @gol -fdump-tree-dse@r{[}-@var{n}@r{]} @gol -fdump-tree-phiprop@r{[}-@var{n}@r{]} @gol @@ -381,8 +382,8 @@ -floop-parallelize-all -flto -flto-compression-level @gol -flto-partition=@var{alg} -flto-report -fmerge-all-constants @gol -fmerge-constants -fmodulo-sched -fmodulo-sched-allow-regmoves @gol --fmove-loop-invariants fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol --fno-default-inline @gol +-fmove-loop-invariants -fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol +-ftsan -ftsan-ignore -fno-default-inline @gol -fno-defer-pop -fno-function-cse -fno-guess-branch-probability @gol -fno-inline -fno-math-errno -fno-peephole -fno-peephole2 @gol -fno-sched-interblock -fno-sched-spec -fno-signed-zeros @gol @@ -5896,6 +5897,11 @@ Dump each function after adding mudflap instrumentation. The file name is made by appending @file{.mudflap} to the source file name. +@item tsan +@opindex fdump-tree-tsan +Dump each function after adding ThreadSanitizer instrumentation. The file name is +made by appending @file{.tsan} to the source file name. + @item sra @opindex fdump-tree-sra Dump each function after performing scalar replacement of aggregates. The @@ -6674,6 +6680,12 @@ some protection against outright memory corrupting writes, but allows erroneously read data to propagate within a program. +@item -ftsan -ftsan-ignore +@opindex ftsan +@opindex ftsan-ignore +Add ThreadSanitizer instrumentation. Use @option{-ftsan-ignore} to specify +an ignore file. Refer to http://go/tsan for details. + @item -fthread-jumps @opindex fthread-jumps Perform optimizations where we check to see if a jump branches to a Index: gcc/tree-tsan.c =================================================================== --- gcc/tree-tsan.c (revision 0) +++ gcc/tree-tsan.c (revision 0) @@ -0,0 +1,1069 @@ +/* ThreadSanitizer instrumentation pass. + http://code.google.com/p/data-race-test + Copyright (C) 2011 + Free Software Foundation, Inc. + Contributed by Dmitry Vyukov <dvyukov@google.com> + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "intl.h" +#include "tm.h" +#include "basic-block.h" +#include "gimple.h" +#include "function.h" +#include "tree-flow.h" +#include "tree-pass.h" +#include "cfghooks.h" +#include "langhooks.h" +#include "output.h" +#include "options.h" +#include "target.h" +#include "cgraph.h" +#include "diagnostic.h" + +#include <stdlib.h> +#include <stdio.h> + +/* ThreadSanitizer is a data race detector for C/C++ programs. + http://code.google.com/p/data-race-test/wiki/ThreadSanitizer + + The tool consists of two parts: + instrumentation module (this file) and a run-time library. + The instrumentation module maintains shadow call stacks + and intercepts interesting memory accesses. + The instrumentation is enabled with -ftsan flag. + + Instrumentation for shadow stack maintenance is as follows: + void somefunc () + { + __tsan_shadow_stack [-1] = __builtin_return_address (0); + __tsan_shadow_stack++; + // function body + __tsan_shadow_stack--; + } + + Interception for memory access interception is as follows: + *addr = 1; + __tsan_handle_mop (addr, flags); + where flags are (is_sblock | (is_store << 1) | ((sizeof (*addr) - 1) << 2). + is_sblock is used merely for optimization purposes and can always + be set to 1, see comments in instrument_mops function. + + Ignore files can be used to selectively non instrument some functions. + Ignore file is specified with -ftsan-ignore=filename flag. + There are 3 types of ignores: (1) do not instrument memory accesses + in the function, (2) do not create sblocks in the function + and (3) recursively ignore memory accesses in the function. + That last ignore type requires additional instrumentation of the form: + void somefunc () + { + __tsan_thread_ignore++; + // function body + __tsan_thread_ignore--; + } + + The run-time library provides __tsan_handle_mop function, + definitions of __tsan_shadow_stack and __tsan_thread_ignore variables, + and intercepts synchronization related functions. */ + +#define TSAN_IGNORE "__tsan_thread_ignore" +#define TSAN_STACK "__tsan_shadow_stack" +#define TSAN_MOP "__tsan_handle_mop" +#define TSAN_PERFIX "__tsan_" +#define MAX_MOP_BYTES 16 +#define SBLOCK_SIZE 5 + +enum tsan_ignore_type +{ + tsan_ignore_none = 1 << 0, /* Do not ignore. */ + tsan_ignore_func = 1 << 1, /* Completely ignore the whole func. */ + tsan_ignore_mop = 1 << 2, /* Do not instrument accesses. */ + tsan_ignore_rec = 1 << 3, /* Do not instrument accesses recursively. */ + tsan_ignore_hist = 1 << 4 /* Do not create superblocks. */ +}; + +/* Info associated with each basic block. + Used to determine super-blocks (see instrument_mops ()). */ + +struct bb_data +{ + int is_visited; + int has_sb; + const char *sb_file; + int sb_line_min; + int sb_line_max; +}; + +/* Memory access descriptor. */ + +struct mop_desc +{ + int is_call; + int is_store; + gimple_stmt_iterator gsi; + tree expr; +}; + +/* Descriptor of an ignore file entry. */ + +struct tsan_ignore_desc +{ + struct tsan_ignore_desc *next; + enum tsan_ignore_type type; + char *name; +}; + +/* Number of instrumented memory accesses in the current function. */ + +static int func_mops; + +/* Number of function calls in the current function. */ + +static int func_calls; + +/* Ignore status for the current function (see tsan_ignore_type). */ + +static enum tsan_ignore_type func_ignore; + +static int ignore_init = 0; +static struct tsan_ignore_desc *ignore_head; + +typedef struct mop_desc mop_desc; +DEF_VEC_O (mop_desc); +DEF_VEC_ALLOC_O (mop_desc, heap); +static VEC (mop_desc, heap) *mop_list; + +/* Returns a definition of a runtime variable with type TYP and name NAME. */ + +static tree +build_var_decl (tree typ, const char *name) +{ + tree id; + tree decl; + varpool_node_ptr var; + + /* Check if a user has defined it for testing. */ + id = get_identifier (name); + var = varpool_node_for_asm (id); + if (var != NULL) + { + decl = var->decl; + gcc_assert (TREE_CODE (decl) == VAR_DECL); + return decl; + } + + decl = build_decl (UNKNOWN_LOCATION, VAR_DECL, id, typ); + TREE_STATIC (decl) = 1; + TREE_PUBLIC (decl) = 1; + DECL_EXTERNAL (decl) = 1; + if (targetm.have_tls) + DECL_TLS_MODEL (decl) = decl_default_tls_model (decl); + TREE_USED (decl) = 1; + TREE_THIS_VOLATILE (decl) = 1; + SET_DECL_ASSEMBLER_NAME (decl, id); + return decl; +} + +/* Builds the following decl + extern __thread void **__tsan_shadow_stack; */ + +static tree +get_shadow_stack_decl (void) +{ + static tree decl; + + if (decl == NULL) + decl = build_var_decl (build_pointer_type (ptr_type_node), TSAN_STACK); + return decl; +} + +/* Builds the following decl + extern __thread int __tsan_thread_ignore; */ + +static tree +get_thread_ignore_decl (void) +{ + static tree decl; + + if (decl == NULL) + decl = build_var_decl (integer_type_node, TSAN_IGNORE); + return decl; +} + +/* Builds the following decl + void __tsan_handle_mop (void *addr, unsigned flags); */ + +static tree +get_handle_mop_decl (void) +{ + tree id; + tree fn_type; + varpool_node_ptr var; + static tree decl; + + if (decl != NULL) + return decl; + + /* Check if a user has defined it for testing. */ + id = get_identifier (TSAN_MOP); + var = varpool_node_for_asm (id); + if (var != NULL) + { + decl = var->decl; + gcc_assert (TREE_CODE (decl) == FUNCTION_DECL); + return decl; + } + + fn_type = build_function_type_list (void_type_node, ptr_type_node, + integer_type_node , NULL_TREE); + decl = build_fn_decl (TSAN_MOP, fn_type); + TREE_NOTHROW (decl) = 1; + DECL_ATTRIBUTES (decl) = tree_cons (get_identifier ("leaf"), + NULL, DECL_ATTRIBUTES (decl)); + DECL_ASSEMBLER_NAME (decl); + return decl; +} + +/* Adds new ignore definition to the global list. + TYPE is the ignore type (see tsan_ignore_type). + NAME is the ignore pattern (e.g. "std*string*insert"). */ + +static void +ignore_append (enum tsan_ignore_type type, char *name) +{ + struct tsan_ignore_desc *desc; + + desc = XCNEW (struct tsan_ignore_desc); + desc->type = type; + desc->name = xstrdup (name); + desc->next = ignore_head; + ignore_head = desc; +} + +/* Checks as to whether identifier STR matches template TEMPL. + Templates can only contain '*', e.g. 'std*string*insert'. + Templates implicitly start and end with '*' + since they are matched against mangled names. + Returns non-zero if STR is matched against TEMPL. */ + +static int +ignore_match (char *templ, const char *str) +{ + char *tpos; + const char *spos; + + while (templ && templ [0]) + { + if (templ [0] == '*') + { + templ++; + continue; + } + if (str [0] == 0) + return 0; + tpos = strchr (templ, '*'); + if (tpos != NULL) + tpos [0] = 0; + spos = strstr (str, templ); + str = spos + strlen (templ); + templ = tpos; + if (tpos != NULL) + tpos [0] = '*'; + if (spos == NULL) + return 0; + } + return 1; +} + +/* Loads ignore definitions from the file specified by -ftsan-ignore=filename. + The result is stored in the global ignore_head list. + Ignore files have the following format: + +# This is a comment - ignored + +# The below line says to not instrument memory accesses +# in all functions that match 'std*string*insert' +fun:std*string*insert + +# The below line says to not instrument memory accesses +# in the function called 'foobar' *and* in all functions +# that it calls recursively +fun_r:foobar + +# The below line says to not create superblocks +# in the function called 'barbaz' +fun_hist:barbaz + +# Ignore all functions in the source file +src:atomic.c + +# Everything else is uninteresting for us (e.g. obj:) +*/ + +static void +ignore_load (void) +{ + FILE *f; + char *line; + size_t linesz; + ssize_t sz; + char buf [PATH_MAX]; + + if(getenv("GCCTSAN_PAUSE")) + { + int res; + printf("ATTACH A DEBUGGER AND PRESS ENTER\n"); + res = scanf("%s", buf); + (void)res; + } + + if (flag_tsan_ignore == NULL || flag_tsan_ignore [0] == 0) + return; + + f = fopen (flag_tsan_ignore, "r"); + if (f == NULL) + { + /* Try to open it relative to main_input_filename. */ + strncpy (buf, main_input_filename, sizeof (buf)); + buf [sizeof (buf) - 1] = 0; + line = strrchr (buf, '/'); + if (line != NULL) + { + line++; + strncpy (line, flag_tsan_ignore, sizeof (buf) - (line - buf)); + buf [sizeof (buf) - 1] = 0; + f = fopen (buf, "r"); + } + } + if (f == NULL) + { + error ("failed to open ignore file '%s'\n", flag_tsan_ignore); + return; + } + + line = 0; + linesz = 0; + while ((sz = getline (&line, &linesz, f)) != -1) + { + if (sz == 0) + continue; + /* Strip line terminator. */ + if (line [sz - 1] == '\r' || line [sz - 1] == '\n') + line [sz - 1] = 0; + if (strncmp (line, "src:", sizeof ("src:") - 1) == 0) + ignore_append (tsan_ignore_func, line + sizeof ("src:") - 1); + else if (strncmp (line, "fun:", sizeof ("fun:") - 1) == 0) + ignore_append (tsan_ignore_mop, line + sizeof ("fun:") - 1); + else if (strncmp (line, "fun_r:", sizeof ("fun_r:") - 1) == 0) + ignore_append (tsan_ignore_rec, line + sizeof ("fun_r:") - 1); + else if (strncmp (line, "fun_hist:", sizeof ("fun_hist:") - 1) == 0) + ignore_append (tsan_ignore_hist, line + sizeof ("fun_hist:") - 1); + /* Other lines are not interesting. */ + } + + free (line); + fclose (f); +} + +/* Returns ignore status for the current function. */ + +static enum tsan_ignore_type +tsan_ignore (void) +{ + const char *func_name; + const char *src_name; + struct tsan_ignore_desc *desc; + + if (ignore_init == 0) + { + ignore_load (); + ignore_init = 1; + } + + src_name = expand_location (cfun->function_start_locus).file; + if (src_name == NULL) + src_name = ""; + + func_name = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (cfun->decl)); + /* Ignore all functions starting with __tsan_ - intended for testing. */ + if (strncmp (func_name, TSAN_PERFIX, sizeof (TSAN_PERFIX) - 1) == 0) + return tsan_ignore_func; + + for (desc = ignore_head; desc; desc = desc->next) + { + if (desc->type == tsan_ignore_func) + { + if (ignore_match (desc->name, src_name)) + return desc->type; + } + else if (ignore_match (desc->name, func_name)) + return desc->type; + } + return tsan_ignore_none; +} + +/* Builds either (__tsan_shadow_stack += 1) or (__tsan_shadow_stack -= 1) + expression depending on DO_DEC parameter. Appends the result to SEQ. */ + +static void +build_stack_op (gimple_seq *seq, bool do_dec) +{ + tree op_size; + double_int op_size_cst; + unsigned long long size_val; + unsigned long long size_valhi; + tree op_expr; + gimple assign; + tree sstack_decl; + gimple_seq s; + + op_size = TYPE_SIZE (ptr_type_node); + op_size_cst = tree_to_double_int (op_size); + size_val = op_size_cst.low / BITS_PER_UNIT; + size_valhi = 0; + if (do_dec) + { + size_val = -size_val; + size_valhi = -1; + } + op_size = build_int_cst_wide (sizetype, size_val, size_valhi); + sstack_decl = get_shadow_stack_decl (); + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, sstack_decl, op_size); + + s = NULL; + op_expr = force_gimple_operand (op_expr, &s, true, NULL_TREE); + gimple_seq_add_seq (seq, s); + + assign = gimple_build_assign (sstack_decl, op_expr); + gimple_seq_add_stmt (seq, assign); +} + +/* Builds either (__tsan_thread_ignore += 1) or (__tsan_thread_ignore -= 1) + expression depending on OP parameter. Stores the result in SEQ. */ + +static void +build_rec_ignore_op (gimple_seq *seq, enum tree_code op) +{ + tree rec_expr; + gimple_seq rec_inc; + gimple rec_assign; + tree ignore_decl; + + ignore_decl = get_thread_ignore_decl (); + rec_expr = build2 (op, integer_type_node, ignore_decl, integer_one_node); + rec_inc = NULL; + rec_expr = force_gimple_operand (rec_expr, &rec_inc, true, NULL_TREE); + gimple_seq_add_seq (seq, rec_inc); + rec_assign = gimple_build_assign (ignore_decl, rec_expr); + gimple_seq_add_stmt (seq, rec_assign); +} + +/* Build the following gimple sequence: + __tsan_shadow_stack [-1] = __builtin_return_address (0); + Stores the result in SEQ. */ + +static void +build_stack_assign (gimple_seq *seq) +{ + tree pc_addr; + tree op_size; + tree op_expr; + tree stack_op; + tree retaddr_decl; + tree assign; + + retaddr_decl = implicit_built_in_decls [BUILT_IN_RETURN_ADDRESS]; + pc_addr = build_call_expr (retaddr_decl, 1, integer_zero_node); + op_size = build_int_cst_wide (sizetype, -(POINTER_SIZE / BITS_PER_UNIT), -1); + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, + get_shadow_stack_decl (), op_size); + stack_op = build1 (INDIRECT_REF, ptr_type_node, op_expr); + assign = build2 (MODIFY_EXPR, ptr_type_node, stack_op, pc_addr); + force_gimple_operand (assign, seq, true, NULL_TREE); +} + +/* Builds the following gimple sequence: + __tsan_handle_mop (&EXPR, + (IS_SBLOCK | (IS_STORE << 1) | ((sizeof (EXPR) - 1) << 2); + The result is stored in GSEQ. */ + +static void +instr_mop (tree expr, int is_store, int is_sblock, gimple_seq *gseq) +{ + tree addr_expr; + tree expr_type; + unsigned size; + unsigned flags; + tree flags_expr; + tree call_expr; + + gcc_assert (gseq != 0 && *gseq == 0); + gcc_assert (is_gimple_addressable (expr)); + + addr_expr = build_addr (unshare_expr (expr), current_function_decl); + expr_type = TREE_TYPE (expr); + while (TREE_CODE (expr_type) == ARRAY_TYPE) + expr_type = TREE_TYPE (expr_type); + size = TREE_INT_CST_LOW (TYPE_SIZE (expr_type)); + size = size / BITS_PER_UNIT; + if (size > MAX_MOP_BYTES) + size = MAX_MOP_BYTES; + size -= 1; + flags = ((!!is_sblock << 0) + (!!is_store << 1) + (size << 2)); + flags_expr = build_int_cst (unsigned_type_node, flags); + call_expr = build_call_expr (get_handle_mop_decl (), + 2, addr_expr, flags_expr); + force_gimple_operand (call_expr, gseq, true, 0); +} + +/* Builds the following gimple sequence: + int is_store = (EXPR != RHS); // The temp is not actually introduced. + __tsan_handle_mop (&EXPR, + (IS_SBLOCK | (IS_STORE << 1) | ((sizeof (EXPR) - 1) << 2); + The result is stored in GSEQ. */ + +static void +instr_vptr_store (tree expr, tree rhs, int is_sblock, gimple_seq *gseq) +{ + tree expr_ptr; + tree addr_expr; + tree expr_type; + tree expr_size; + double_int size; + unsigned flags; + tree flags_expr; + gimple_seq flags_seq; + gimple collect; + tree is_store_expr; + + expr_ptr = build_addr (unshare_expr (expr), current_function_decl); + addr_expr = force_gimple_operand (expr_ptr, gseq, true, NULL_TREE); + expr_type = TREE_TYPE (expr); + while (TREE_CODE (expr_type) == ARRAY_TYPE) + expr_type = TREE_TYPE (expr_type); + expr_size = TYPE_SIZE (expr_type); + size = tree_to_double_int (expr_size); + gcc_assert (size.high == 0 && size.low != 0); + if (size.low > 128) + size.low = 128; + size.low = (size.low / 8) - 1; + flags = ((!!is_sblock << 0) + (size.low << 2)); + flags_expr = build_int_cst (unsigned_type_node, flags); + is_store_expr = build2 (NE_EXPR, unsigned_type_node, + build1 (VIEW_CONVERT_EXPR, size_type_node, expr), + build1 (VIEW_CONVERT_EXPR, size_type_node, rhs)); + is_store_expr = build2 (LSHIFT_EXPR, unsigned_type_node, + is_store_expr, integer_one_node); + flags_expr = build2 (BIT_IOR_EXPR, unsigned_type_node, + is_store_expr, flags_expr); + flags_seq = 0; + flags_expr = force_gimple_operand (flags_expr, &flags_seq, true, NULL_TREE); + gimple_seq_add_seq (gseq, flags_seq); + collect = gimple_build_call ( + get_handle_mop_decl (), 2, addr_expr, flags_expr); + gimple_seq_add_stmt (gseq, collect); +} + +/* Returns true if function entry and exit need to be instrumented. */ + +static bool +is_func_instrumentation_required (void) +{ + if (func_calls == 0 && func_mops == 0) + return false; + if (func_ignore != tsan_ignore_rec) + return true; + if (func_ignore == tsan_ignore_rec && func_calls != 0) + return true; + return false; +} + +/* Returns gimple seq that needs to be inserted at function entry. */ + +static gimple_seq +build_func_entry_instr (void) +{ + gimple_seq gs; + + gs = NULL; + gcc_assert (is_func_instrumentation_required ()); + if (func_ignore != tsan_ignore_rec) + { + build_stack_assign (&gs); + build_stack_op (&gs, false); + } + else + build_rec_ignore_op (&gs, PLUS_EXPR); + return gs; +} + +/* Returns gimple seq that needs to be inserted before function exit. */ + +static gimple_seq +build_func_exit_instr (void) +{ + gimple_seq gs; + + gs = NULL; + gcc_assert (is_func_instrumentation_required ()); + if (func_ignore != tsan_ignore_rec) + build_stack_op (&gs, true); + else + build_rec_ignore_op (&gs, MINUS_EXPR); + return gs; +} + +/* Sets location LOC for all gimples in the SEQ. */ + +static void +set_location (gimple_seq seq, location_t loc) +{ + gimple_seq_node n; + + for (n = gimple_seq_first (seq); n != NULL; n = n->next) + gimple_set_location (n->stmt, loc); + verify_gimple_in_seq (seq); +} + +/* Check as to whether EXPR refers to a store to vptr. */ + +static tree +is_vptr_store (gimple stmt, tree expr, int is_store) +{ + if (is_store == 1 + && gimple_assign_single_p (stmt) + && TREE_CODE (expr) == COMPONENT_REF) + { + tree field = TREE_OPERAND (expr, 1); + if (TREE_CODE (field) == FIELD_DECL + && DECL_VIRTUAL_P (field)) + return gimple_assign_rhs1 (stmt); + } + return NULL; +} + +/* Checks as to whether EXPR refers to constant var/field/param. + Don't bother to instrument them. */ + +static int +is_load_of_const (tree expr, int is_store) +{ + if (is_store) + return 0; + if (TREE_CODE (expr) == COMPONENT_REF) + expr = TREE_OPERAND (expr, 1); + if (TREE_CODE (expr) == VAR_DECL + || TREE_CODE (expr) == PARM_DECL + || TREE_CODE (expr) == FIELD_DECL) + { + if (TREE_READONLY (expr)) + return 1; + } + return 0; +} + +/* Checks as to whether EXPR needs to be instrumented, + if so puts it into the MOP_LIST. + GSI is the iterator from which EXPR was extracted. + IS_STORE says as to whether EXPR refers to a memory store + or a memory load. */ + +static void +handle_expr (gimple_stmt_iterator gsi, tree expr, int is_store, + VEC (mop_desc, heap) **mop_list) +{ + enum tree_code tcode; + struct mop_desc mop; + unsigned fld_off; + unsigned fld_size; + + tcode = TREE_CODE (expr); + + /* Below are things we do not instrument + (no possibility of races or not implemented yet). */ + if ((func_ignore & (tsan_ignore_mop | tsan_ignore_rec)) + || get_base_address (expr) == NULL + /* Compiler-emitted artificial variables. */ + || (DECL_P (expr) && DECL_ARTIFICIAL (expr)) + /* The var does not live in memory -> no possibility of races. */ + || (tcode == VAR_DECL + && TREE_ADDRESSABLE (expr) == 0 + && DECL_EXTERNAL (expr) == 0) + /* TODO (dvyukov): not implemented. */ + || TREE_CODE (TREE_TYPE (expr)) == RECORD_TYPE + /* TODO (dvyukov): not implemented. */ + || tcode == CONSTRUCTOR + /* TODO (dvyukov): not implemented. */ + || tcode == PARM_DECL + /* Load of a const variable/parameter/field. */ + || is_load_of_const (expr, is_store)) + return; + + if (tcode == COMPONENT_REF) + { + tree field = TREE_OPERAND (expr, 1); + if (TREE_CODE (field) == FIELD_DECL) + { + fld_off = TREE_INT_CST_LOW (DECL_FIELD_BIT_OFFSET (field)); + fld_size = TREE_INT_CST_LOW (DECL_SIZE (field)); + if (((fld_off % BITS_PER_UNIT) != 0) + || ((fld_size % BITS_PER_UNIT) != 0)) + { + /* As of now it crashes compilation. + TODO (dvyukov): handle bit-fields as if touching + the whole field. */ + return; + } + } + } + + /* TODO (dvyukov): handle other cases + (FIELD_DECL, MEM_REF, ARRAY_RANGE_REF, TARGET_MEM_REF, ADDR_EXPR). */ + if (tcode != ARRAY_REF + && tcode != VAR_DECL + && tcode != COMPONENT_REF + && tcode != INDIRECT_REF + && tcode != MEM_REF) + return; + + mop.is_call = 0; + mop.gsi = gsi; + mop.expr = expr; + mop.is_store = is_store; + VEC_safe_push (mop_desc, heap, *mop_list, &mop); +} + +/* Collects all interesting memory accesses from the gimple pointed to by GSI + into MOP_LIST. */ + +static void +handle_gimple (gimple_stmt_iterator gsi, VEC (mop_desc, heap) **mop_list) +{ + unsigned i; + struct mop_desc mop; + gimple stmt; + enum gimple_code gcode; + tree rhs; + tree lhs; + + stmt = gsi_stmt (gsi); + gcode = gimple_code (stmt); + if (gcode >= LAST_AND_UNUSED_GIMPLE_CODE) + return; + + switch (gcode) + { + case GIMPLE_CALL: + { + func_calls += 1; + memset (&mop, 0, sizeof (mop)); + mop.is_call = 1; + VEC_safe_push (mop_desc, heap, *mop_list, &mop); + break; + } + + case GIMPLE_ASSIGN: + { + /* Handle assignment lhs as store. */ + lhs = gimple_assign_lhs (stmt); + handle_expr (gsi, lhs, 1, mop_list); + + /* Handle operands as loads. */ + for (i = 1; i < gimple_num_ops (stmt); i++) + { + rhs = gimple_op (stmt, i); + handle_expr (gsi, rhs, 0, mop_list); + } + break; + } + + default: + break; + } +} + +/* Instruments single basic block BB. + BBD is the sblock info associated with the block. */ + +static void +instrument_bblock (struct bb_data *bbd, basic_block bb) +{ + int ix; + int is_sblock; + gimple_stmt_iterator gsi; + struct mop_desc *mop; + gimple stmt; + location_t loc; + expanded_location eloc; + gimple_seq instr_seq; + tree rhs; + + /* Iterate over all gimples and collect interesting mops into mop_list. */ + VEC_free (mop_desc, heap, mop_list); + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + handle_gimple (gsi, &mop_list); + } + + mop = 0; + for (ix = 0; VEC_iterate (mop_desc, mop_list, ix, mop); ix += 1) + { + if (mop->is_call != 0) + { + /* After a function call we must start a brand new sblock, + because the function can contain synchronization. */ + bbd->has_sb = 0; + continue; + } + + func_mops += 1; + stmt = gsi_stmt (mop->gsi); + loc = gimple_location (stmt); + eloc = expand_location (loc); + + /* Check as to whether we may not set sblock flag for the access. */ + is_sblock = (bbd->has_sb == 0 + || !(eloc.file != 0 + && bbd->sb_file != 0 + && strcmp (eloc.file, bbd->sb_file) == 0 + && eloc.line >= bbd->sb_line_min + && eloc.line <= bbd->sb_line_max)); + + if (func_ignore == tsan_ignore_hist) + is_sblock = 0; + + if (is_sblock) + { + /* Start new sblock with new source info. */ + bbd->has_sb = 1; + bbd->sb_file = eloc.file; + bbd->sb_line_min = eloc.line; + bbd->sb_line_max = eloc.line + SBLOCK_SIZE; + } + + instr_seq = 0; + rhs = is_vptr_store (stmt, mop->expr, mop->is_store); + if (rhs == NULL) + instr_mop (mop->expr, mop->is_store, is_sblock, &instr_seq); + else + instr_vptr_store (mop->expr, rhs, is_sblock, &instr_seq); + gcc_assert (instr_seq != 0); + set_location (instr_seq, loc); + /* Instrumentation for assignment of a function result + must be inserted after the call. Instrumentation for + reads of function arguments must be inserted before the call. + That's because the call can contain synchronization. */ + if (is_gimple_call (stmt) && mop->is_store == 1) + gsi_insert_seq_after (&mop->gsi, instr_seq, GSI_NEW_STMT); + else + gsi_insert_seq_before (&mop->gsi, instr_seq, GSI_SAME_STMT); + } +} + +/* Instruments all interesting memory accesses in the current function. */ + +static void +instrument_mops (void) +{ + basic_block bb; + int *blocks_inverted; + struct bb_data *bb_data; + struct bb_data *pred; + struct bb_data *bbd; + edge e; + edge_iterator ei; + int sb_line_min, sb_line_max; + int cnt, i; + + /* The function does basic block traversal in reverse top sort order + of the inverted CFG. Such order is required to properly mark super-blocks. + The idea behind super-blocks is as follows. + If several memory accesses happen within SBLOCK_SIZE source code lines + from each other, then we only mark the first access as SBLOCK. + This allows the runtime library to memorize a stack trace + only for the first access and do not memorize for others. + This significantly reduces memory consumption in exchange for slightly + imprecise stack traces for previous accesses. */ + + blocks_inverted = XNEWVEC (int, last_basic_block + NUM_FIXED_BLOCKS); + bb_data = XCNEWVEC (struct bb_data, last_basic_block + NUM_FIXED_BLOCKS); + cnt = inverted_post_order_compute (blocks_inverted); + for (i = 0; i < cnt; i++) + { + bb = BASIC_BLOCK (blocks_inverted [i]); + bbd = &bb_data [bb->index]; + /* Iterate over all predecessors and merge their sblock info. */ + FOR_EACH_EDGE (e, ei, bb->preds) + { + pred = &bb_data [e->src->index]; + if (!pred->is_visited || !pred->has_sb || pred == bbd) + { + /* If there is a not visited predecessor, + or a predecessor with no active sblock info, + or a self-loop, then we will have to start + a brand new sblock on next memory access. */ + bbd->has_sb = 0; + break; + } + else if (bbd->has_sb == 0) + { + /* If it's a first predecessor, just copy the info. */ + bbd->has_sb = 1; + bbd->sb_file = pred->sb_file; + bbd->sb_line_min = pred->sb_line_min; + bbd->sb_line_max = pred->sb_line_max; + } + else + { + /* Otherwise, find the interception + between two sblock descriptors. */ + bbd->has_sb = 0; + if (bbd->sb_file != 0 && pred->sb_file != 0 + && strcmp (bbd->sb_file, pred->sb_file) == 0) + { + sb_line_min = MAX (bbd->sb_line_min, pred->sb_line_min); + sb_line_max = MIN (bbd->sb_line_max, pred->sb_line_max); + if (sb_line_min <= sb_line_max) + { + bbd->has_sb = 1; + bbd->sb_line_min = sb_line_min; + bbd->sb_line_max = sb_line_max; + } + } + /* No interception, have to start new sblock. */ + if (bbd->has_sb == 0) + break; + } + } + + instrument_bblock (bbd, bb); + bbd->is_visited = 1; + } + + free (blocks_inverted); + free (bb_data); +} + +/* Instruments function entry. */ + +static void +instrument_func_entry (void) +{ + gimple_seq seq; + basic_block entry_bb; + edge entry_edge; + gimple_stmt_iterator gsi; + + /* Insert new BB before the first BB. */ + seq = build_func_entry_instr (); + gcc_assert (seq != NULL); + entry_bb = ENTRY_BLOCK_PTR; + entry_edge = single_succ_edge (entry_bb); + set_location (seq, cfun->function_start_locus); + entry_bb = split_edge (entry_edge); + gsi = gsi_start_bb (entry_bb); + gsi_insert_seq_after (&gsi, seq, GSI_NEW_STMT); +} + +/* Instruments function exits. */ + +static void +instrument_func_exit (void) +{ + location_t loc; + gimple_seq seq; + basic_block exit_bb; + gimple_stmt_iterator gsi; + gimple stmt; + edge e; + edge_iterator ei; + + /* Find all function exits. */ + exit_bb = EXIT_BLOCK_PTR; + FOR_EACH_EDGE (e, ei, exit_bb->preds) + { + gsi = gsi_last_bb (e->src); + stmt = gsi_stmt (gsi); + gcc_assert (gimple_code (stmt) == GIMPLE_RETURN); + loc = gimple_location (stmt); + seq = build_func_exit_instr (); + gcc_assert (seq != NULL); + set_location (seq, loc); + gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT); + } +} + +/* ThreadSanitizer instrumentation pass. */ + +static unsigned +tsan_pass (void) +{ + gimplify_ctx gctx; + + func_ignore = tsan_ignore (); + if (func_ignore == tsan_ignore_func) + return 0; + + func_calls = 0; + func_mops = 0; + + push_gimplify_context (&gctx); + + instrument_mops (); + + if (is_func_instrumentation_required ()) + { + instrument_func_entry (); + instrument_func_exit (); + } + + pop_gimplify_context (NULL); + + return 0; +} + +/* The pass's gate. */ + +static bool +tsan_gate (void) +{ + return flag_tsan != 0; +} + +/* The pass descriptor. */ + +struct gimple_opt_pass pass_tsan = {{ + GIMPLE_PASS, + "tsan", /* name */ + tsan_gate, /* gate */ + tsan_pass, /* execute */ + NULL, /* sub */ + NULL, /* next */ + 0, /* static_pass_number */ + TV_NONE, /* tv_id */ + PROP_trees | PROP_cfg, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + TODO_dump_cgraph | TODO_dump_func | TODO_verify_all + | TODO_update_ssa | TODO_update_address_taken /* todo_flags_finish */ +}}; + Property changes on: gcc/tree-tsan.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/tree-pass.h =================================================================== --- gcc/tree-pass.h (revision 180522) +++ gcc/tree-pass.h (working copy) @@ -352,6 +352,7 @@ extern struct gimple_opt_pass pass_mudflap_1; extern struct gimple_opt_pass pass_mudflap_2; +extern struct gimple_opt_pass pass_tsan; extern struct gimple_opt_pass pass_lower_cf; extern struct gimple_opt_pass pass_refactor_eh; extern struct gimple_opt_pass pass_lower_eh; Index: gcc/testsuite/gcc.dg/tsan-ignore.ignore =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) @@ -0,0 +1,7 @@ +#comment +fun:foo +fun:*bar +fun:baz* +fun:*bla* +fun:x*x +src:tsan-ignore.h Index: gcc/testsuite/gcc.dg/tsan.h =================================================================== --- gcc/testsuite/gcc.dg/tsan.h (revision 0) +++ gcc/testsuite/gcc.dg/tsan.h (revision 0) @@ -0,0 +1,101 @@ +/* Helper declarations and functions for ThreadSanitizer instrumentation (-ftsan) testing */ + +int printf (char *str, ...); +void exit (int); + +/* Variables referenced by the instrumentation. */ + +__thread void **__tsan_shadow_stack; +__thread int __tsan_thread_ignore; + +/* Local helper vars. */ + +__thread void *shadow_stack[1024]; +__thread int pad[1024]; +__thread int mop_expect = 0; +__thread int mop_depth = 0; +__thread void* mop_addr = 0; +__thread unsigned long long mop_pc = 0; +__thread unsigned mop_flags = 0; +__thread unsigned mop_line = 0; + +/* Setups shadow stack var (not instrumented). */ + +void __attribute__ ((constructor)) +__tsan_init (void) +{ + __tsan_shadow_stack = shadow_stack; +} + +/* Declare that we expect an instrumented memory access (not instrumented). + depth - stack depth of the mop (0 - main, 1 - func called from main and so on). + addr - memory access address. + is_store - store/load. + is_sblock - superblock flag of the access. + size - memory access size in bytes. */ + +void +__tsan_expect_mop (int depth, void const volatile *addr, int is_store, + int is_sblock, int size, unsigned line) +{ + if (mop_expect) + { + printf ("missed mop: addr=%p pc=%d line=%d\n", mop_addr, mop_pc, mop_line); + exit (1); + } + + mop_expect = 1; + mop_depth = depth; + mop_addr = (void*)addr; + mop_pc = (unsigned long long)__builtin_return_address(0); + mop_flags = !!is_sblock | (!!is_store << 1) | ((size - 1) << 2); + mop_line = line; +} + +/* Memory access function (referenced by instrumentation, not instrumented). */ + +void +__tsan_handle_mop (void *addr, unsigned flags) +{ + unsigned long long pc; + int depth; + + printf ("mop: addr=%p flags=%x called from %p line=%d\n", + addr, flags, __tsan_shadow_stack [-2], mop_line); + if (mop_expect == 0) + return; + + /* Verify parameters with what we expect. */ + + if (addr != mop_addr) + { + printf ("incorrect mop addr: %p/%p line=%d\n", + addr, mop_addr, mop_line); + exit (1); + } + + pc = (unsigned long long)__builtin_return_address(0); + if (pc < mop_pc - 100 || pc > mop_pc + 100) + { + printf ("incorrect mop pc: %p/%p line=%d\n", + (void*)pc, (void*)mop_pc, mop_line); + exit (1); + } + + depth = __tsan_shadow_stack - shadow_stack - 1; + if (depth != mop_depth) + { + printf ("incorrect mop depth: %d/%d line=%d\n", + depth, mop_depth, mop_line); + exit (1); + } + + if (flags != mop_flags) + { + printf ("incorrect mop flags: %x/%x line=%d\n", + flags, mop_flags, mop_line); + exit (1); + } + + mop_expect = 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan.h ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-ignore.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) @@ -0,0 +1,49 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan -O1 -ftsan-ignore=tsan-ignore.ignore" } */ +#include "tsan.h" +#include "tsan-ignore.h" + +/* Check ignore file handling. */ + +int +foo (int *p) +{ + p [0] = 1; +} + +int bar (int *p) +{ + p [0] = 1; +} + +int baz (int *p) +{ + p [0] = 1; +} + +int bla (int *p) +{ + p [0] = 1; +} + +int xxx (int *p) +{ + p [0] = 1; +} + +int +main (void) +{ + int p, x; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + /* All these functions must be ignored. */ + foo (&x); + bar (&x); + baz (&x); + bla (&x); + xxx (&x); + in_tsan_ignore_header (&x); + p = 0; + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-ignore.h =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) @@ -0,0 +1,5 @@ +int +in_tsan_ignore_header (int *p) +{ + p [0] = 1; +} Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.h ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-stack.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) @@ -0,0 +1,34 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan -O1" } */ +#include "tsan.h" + +/* Check shadow stack maintance. */ + +void barbaz (int volatile *p) +{ + __tsan_expect_mop(2, p, 1, 1, sizeof(*p), __LINE__); + *p = 11; +} + +void +foobar (int volatile *p) +{ + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + p[0] = 13; + barbaz (p); + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + p[0] = 17; +} + +int +main (void) +{ + int volatile p; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + p = 0; + foobar (&p); + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + p = 1; + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-stack.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-mop.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) @@ -0,0 +1,93 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan -O1" } */ +#include "tsan.h" + +/* Sanity check for memory accesses instrumentation. */ + +typedef struct C C; +struct C +{ + int r, g, b; +}; + +typedef struct P P; +struct P +{ + int x, y; + C c; + C *cp; + C ca[2]; +}; + +void teststructp (P *p) +{ + int volatile tmp; + + __tsan_expect_mop(2, &p->x, 1, 1, sizeof(p->x), __LINE__); + p->x = 42; + + __tsan_expect_mop(2, &p->x, 0, 1, sizeof(p->x), __LINE__); + tmp = p->x; + +} + +void testfor (P *p) +{ + long long volatile tmp = 0; + long long *a = (long long *)p->ca; + int i; + for (i = 0; i < 2; i++) + { + __tsan_expect_mop(2, &a [i], 0, 1, sizeof(a [0]), __LINE__); + tmp += a [i]; + } +} + +void +teststruct (void) +{ + P p; + int volatile tmp; + + __tsan_expect_mop(1, &p.x, 1, 1, sizeof(p.x), __LINE__); + p.x = 42; + + __tsan_expect_mop(1, &p.x, 0, 1, sizeof(p.x), __LINE__); + tmp = p.x; + + __tsan_expect_mop(1, &p.cp, 1, 1, sizeof(p.cp), __LINE__); + p.cp = &p.c; + + __tsan_expect_mop(1, &p.c.r, 1, 1, sizeof(p.c.r), __LINE__); + p.c.r = 11; + + teststructp (&p); + testfor (&p); +} + +void +foobar (int *p) +{ + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + p[0] = 1; + + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + *p = 2; + + __tsan_expect_mop(1, (char*)p+3, 1, 1, 1, __LINE__); + *((char*)p+3) = 3; +} + +int +main (void) +{ + int p; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + p = 0; + foobar (&p); + + teststruct (); + + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-mop.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/common.opt =================================================================== --- gcc/common.opt (revision 180522) +++ gcc/common.opt (working copy) @@ -1547,6 +1547,14 @@ Common RejectNegative Report Var(flag_mudflap_ignore_reads) Ignore read operations when inserting mudflap instrumentation +ftsan +Common RejectNegative Report Var(flag_tsan) +Add ThreadSanitizer instrumentation + +ftsan-ignore= +Common RejectNegative Joined Var(flag_tsan_ignore) +-ftsan-ignore=filename ThreadSanitizer ignore file + fdce Common Var(flag_dce) Init(1) Optimization Use the RTL dead code elimination pass Index: gcc/Makefile.in =================================================================== --- gcc/Makefile.in (revision 180522) +++ gcc/Makefile.in (working copy) @@ -1494,6 +1494,7 @@ tree-streamer-out.o \ tree-tailcall.o \ tree-threadsafe-analyze.o \ + tree-tsan.o \ tree-vect-generic.o \ tree-vect-patterns.o \ tree-vect-data-refs.o \ @@ -2814,6 +2815,12 @@ $(C_TREE_H) $(C_COMMON_H) $(GIMPLE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) \ output.h langhooks.h tree-mudflap.h $(TM_H) coretypes.h \ $(GGC_H) gt-tree-mudflap.h $(TREE_PASS_H) $(DIAGNOSTIC_CORE_H) +tree-tsan.o : $(CONFIG_H) $(SYSTEM_H) $(TREE_H) $(TREE_INLINE_H) \ + $(GIMPLE_H) $(DIAGNOSTIC_H) langhooks.h \ + $(TM_H) coretypes.h $(TREE_DUMP_H) $(TREE_PASS_H) $(CGRAPH_H) $(GGC_H) \ + $(BASIC_BLOCK_H) $(FLAGS_H) $(FUNCTION_H) \ + $(TM_P_H) $(TREE_FLOW_H) $(DIAGNOSTIC_CORE_H) $(GIMPLE_H) tree-iterator.h \ + intl.h cfghooks.h output.h options.h c-family/c-common.h tree-pretty-print.o : tree-pretty-print.c $(CONFIG_H) $(SYSTEM_H) \ $(TREE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) $(TREE_FLOW_H) \ $(TM_H) coretypes.h tree-iterator.h $(SCEV_H) langhooks.h \ Index: gcc/passes.c =================================================================== --- gcc/passes.c (revision 180522) +++ gcc/passes.c (working copy) @@ -1344,6 +1344,7 @@ NEXT_PASS (pass_split_crit_edges); NEXT_PASS (pass_pre); NEXT_PASS (pass_sink_code); + NEXT_PASS (pass_tsan); NEXT_PASS (pass_tree_loop); { struct opt_pass **p = &pass_tree_loop.pass.sub; -- This patch is available for review at http://codereview.appspot.com/5303083
Sign in to reply to this message.
On 2011/11/01 16:59:04, davidxl wrote: > that means some existing bugs get exposed. Your previous version > simply skipped the target mem refs. You will need to debug the > problem a little more. David, I've fixed the crashes. PTAL.
Sign in to reply to this message.
http://codereview.appspot.com/5303083/diff/28001/gcc/tree-tsan.c File gcc/tree-tsan.c (right): http://codereview.appspot.com/5303083/diff/28001/gcc/tree-tsan.c#newcode227 gcc/tree-tsan.c:227: var = varpool_node_for_asm (id); Use cgraph_node_for_asm instead.
Sign in to reply to this message.
Have you run through SPEC, and SPEC06 with this change? What is the instrumentation overhead using gcc? David
Sign in to reply to this message.
On Thu, Nov 10, 2011 at 4:00 PM, <davidxl@google.com> wrote: > > Have you run through SPEC, and SPEC06 with this change? What is the > instrumentation overhead using gcc? > I don't think anyone of us ever run spec with tsan. Mostly because this will always use the fast path of the tsan analysis (spec is single-threaded). --kcc > David > > http://codereview.appspot.com/**5303083/<http://codereview.appspot.com/5303083/> >
Sign in to reply to this message.
On Thu, Nov 10, 2011 at 4:24 PM, Kostya Serebryany <kcc@google.com> wrote: > > > On Thu, Nov 10, 2011 at 4:00 PM, <davidxl@google.com> wrote: >> >> Have you run through SPEC, and SPEC06 with this change? What is the >> instrumentation overhead using gcc? > > I don't think anyone of us ever run spec with tsan. > Mostly because this will always use the fast path of the tsan analysis (spec > is single-threaded). I suggested it because It is good for correctness testing, instrumentation (only) overhead testing. David > --kcc >> >> David >> >> http://codereview.appspot.com/5303083/ > >
Sign in to reply to this message.
On Thu, Nov 10, 2011 at 4:30 PM, Xinliang David Li <davidxl@google.com>wrote: > On Thu, Nov 10, 2011 at 4:24 PM, Kostya Serebryany <kcc@google.com> wrote: > > > > > > On Thu, Nov 10, 2011 at 4:00 PM, <davidxl@google.com> wrote: > >> > >> Have you run through SPEC, and SPEC06 with this change? What is the > >> instrumentation overhead using gcc? > > > > I don't think anyone of us ever run spec with tsan. > > Mostly because this will always use the fast path of the tsan analysis > (spec > > is single-threaded). > > I suggested it because It is good for correctness testing, > instrumentation (only) overhead testing. > Indeed, it might give us some idea about the instrumentation-only overhead. --kcc > > David > > > --kcc > >> > >> David > >> > >> http://codereview.appspot.com/5303083/ > > > > >
Sign in to reply to this message.
The patch is for google/main branch. ThreadSanitizer is a data race detector for C/C++ programs. http://code.google.com/p/data-race-test/wiki/ThreadSanitizer The tool consists of two parts: instrumentation module (this file) and a run-time library. The instrumentation module maintains shadow call stacks and intercepts interesting memory accesses. The instrumentation is enabled with -ftsan flag. Instrumentation for shadow stack maintenance is as follows: void somefunc () { __tsan_shadow_stack [-1] = __builtin_return_address (0); __tsan_shadow_stack++; // function body __tsan_shadow_stack--; } Interception for memory access interception is as follows: *addr = 1; __tsan_handle_mop (addr, flags); where flags are (is_sblock | (is_store << 1) | ((sizeof (*addr) - 1) << 2). is_sblock is used merely for optimization purposes and can always be set to 1, see comments in instrument_mops function. Ignore files can be used to selectively non instrument some functions. Ignore file is specified with -ftsan-ignore=filename flag. There are 3 types of ignores: (1) do not instrument memory accesses in the function, (2) do not create sblocks in the function and (3) recursively ignore memory accesses in the function. That last ignore type requires additional instrumentation of the form: void somefunc () { __tsan_thread_ignore++; // function body __tsan_thread_ignore--; } The run-time library provides __tsan_handle_mop function, definitions of __tsan_shadow_stack and __tsan_thread_ignore variables, and intercepts synchronization related functions. 2011-11-14 Dmitriy Vyukov <dvyukov@google.com> * gcc/doc/invoke.texi: * gcc/tree-tsan.c (enum tsan_ignore_type): (struct bb_data): (struct mop_desc): (struct tsan_ignore_desc): (build_var_decl): (get_shadow_stack_decl): (get_thread_ignore_decl): (build_func_decl): (get_handle_mop_decl): (get_init_decl): (ignore_append): (ignore_match): (ignore_load): (tsan_ignore): (build_stack_op): (build_rec_ignore_op): (build_stack_assign): (instr_mop): (instr_vptr_store): (is_func_instrumentation_required): (build_func_entry_instr): (build_func_exit_instr): (set_location): (is_vptr_store): (is_load_of_const): (handle_expr): (handle_gimple): (instrument_bblock): (instrument_mops): (instrument_func_entry): (instrument_func_exit): (tsan_pass): (tsan_gate): (void tsan_finish_file): * gcc/tree-tsan.h: * gcc/tree-pass.h: * gcc/toplev.c: * gcc/testsuite/gcc.dg/tsan-ignore.ignore: * gcc/testsuite/gcc.dg/tsan.h (__tsan_init): (__tsan_expect_mop): (__tsan_handle_mop): * gcc/testsuite/gcc.dg/tsan-ignore.c (foo): (int bar): (int baz): (int bla): (int xxx): (main): * gcc/testsuite/gcc.dg/tsan-ignore.h (in_tsan_ignore_header): * gcc/testsuite/gcc.dg/tsan-stack.c (void barbaz): (foobar): * gcc/testsuite/gcc.dg/tsan-mop.c (struct C): (struct P): (void teststructp): (void testfor): (teststruct): * gcc/common.opt: * gcc/Makefile.in: * gcc/passes.c: Index: gcc/doc/invoke.texi =================================================================== --- gcc/doc/invoke.texi (revision 181347) +++ gcc/doc/invoke.texi (working copy) @@ -308,6 +308,7 @@ -fdump-tree-ssa@r{[}-@var{n}@r{]} -fdump-tree-pre@r{[}-@var{n}@r{]} @gol -fdump-tree-ccp@r{[}-@var{n}@r{]} -fdump-tree-dce@r{[}-@var{n}@r{]} @gol -fdump-tree-gimple@r{[}-raw@r{]} -fdump-tree-mudflap@r{[}-@var{n}@r{]} @gol +-fdump-tree-tsan@r{[}-@var{n}@r{]} @gol -fdump-tree-dom@r{[}-@var{n}@r{]} @gol -fdump-tree-dse@r{[}-@var{n}@r{]} @gol -fdump-tree-phiprop@r{[}-@var{n}@r{]} @gol @@ -381,8 +382,8 @@ -floop-parallelize-all -flto -flto-compression-level @gol -flto-partition=@var{alg} -flto-report -fmerge-all-constants @gol -fmerge-constants -fmodulo-sched -fmodulo-sched-allow-regmoves @gol --fmove-loop-invariants fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol --fno-default-inline @gol +-fmove-loop-invariants -fmudflap -fmudflapir -fmudflapth -fno-branch-count-reg @gol +-ftsan -ftsan-ignore -fno-default-inline @gol -fno-defer-pop -fno-function-cse -fno-guess-branch-probability @gol -fno-inline -fno-math-errno -fno-peephole -fno-peephole2 @gol -fno-sched-interblock -fno-sched-spec -fno-signed-zeros @gol @@ -5896,6 +5897,11 @@ Dump each function after adding mudflap instrumentation. The file name is made by appending @file{.mudflap} to the source file name. +@item tsan +@opindex fdump-tree-tsan +Dump each function after adding ThreadSanitizer instrumentation. The file name is +made by appending @file{.tsan} to the source file name. + @item sra @opindex fdump-tree-sra Dump each function after performing scalar replacement of aggregates. The @@ -6680,6 +6686,12 @@ some protection against outright memory corrupting writes, but allows erroneously read data to propagate within a program. +@item -ftsan -ftsan-ignore +@opindex ftsan +@opindex ftsan-ignore +Add ThreadSanitizer instrumentation. Use @option{-ftsan-ignore} to specify +an ignore file. Refer to http://go/tsan for details. + @item -fthread-jumps @opindex fthread-jumps Perform optimizations where we check to see if a jump branches to a Index: gcc/tree-tsan.c =================================================================== --- gcc/tree-tsan.c (revision 0) +++ gcc/tree-tsan.c (revision 0) @@ -0,0 +1,1114 @@ +/* ThreadSanitizer, a data race detector. + Copyright (C) 2011 Free Software Foundation, Inc. + Contributed by Dmitry Vyukov <dvyukov@google.com> + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "intl.h" +#include "tm.h" +#include "basic-block.h" +#include "gimple.h" +#include "function.h" +#include "tree-flow.h" +#include "tree-pass.h" +#include "tree-iterator.h" +#include "cfghooks.h" +#include "langhooks.h" +#include "output.h" +#include "options.h" +#include "target.h" +#include "cgraph.h" +#include "diagnostic.h" + +#include <stdlib.h> +#include <stdio.h> + +/* ThreadSanitizer is a data race detector for C/C++ programs. + http://code.google.com/p/data-race-test/wiki/ThreadSanitizer + + The tool consists of two parts: + instrumentation module (this file) and a run-time library. + The instrumentation module maintains shadow call stacks + and intercepts interesting memory accesses. + The instrumentation is enabled with -ftsan flag. + + Instrumentation for shadow stack maintenance is as follows: + void somefunc () + { + __tsan_shadow_stack [-1] = __builtin_return_address (0); + __tsan_shadow_stack++; + // function body + __tsan_shadow_stack--; + } + + Interception for memory access interception is as follows: + *addr = 1; + __tsan_handle_mop (addr, flags); + where flags are (is_sblock | (is_store << 1) | ((sizeof (*addr) - 1) << 2). + is_sblock is used merely for optimization purposes and can always + be set to 1, see comments in instrument_mops function. + + Ignore files can be used to selectively non instrument some functions. + Ignore file is specified with -ftsan-ignore=filename flag. + There are 3 types of ignores: (1) do not instrument memory accesses + in the function, (2) do not create sblocks in the function + and (3) recursively ignore memory accesses in the function. + That last ignore type requires additional instrumentation of the form: + void somefunc () + { + __tsan_thread_ignore++; + // function body + __tsan_thread_ignore--; + } + + The run-time library provides __tsan_handle_mop function, + definitions of __tsan_shadow_stack and __tsan_thread_ignore variables, + and intercepts synchronization related functions. */ + +#define TSAN_IGNORE "__tsan_thread_ignore" +#define TSAN_STACK "__tsan_shadow_stack" +#define TSAN_MOP "__tsan_handle_mop" +#define TSAN_INIT "__tsan_init" +#define TSAN_PERFIX "__tsan_" +#define MAX_MOP_BYTES 16 +#define SBLOCK_SIZE 5 + +enum tsan_ignore_type +{ + tsan_ignore_none = 1 << 0, /* Do not ignore. */ + tsan_ignore_func = 1 << 1, /* Completely ignore the whole func. */ + tsan_ignore_mop = 1 << 2, /* Do not instrument accesses. */ + tsan_ignore_rec = 1 << 3, /* Do not instrument accesses recursively. */ + tsan_ignore_hist = 1 << 4 /* Do not create superblocks. */ +}; + +/* Info associated with each basic block. + Used to determine super-blocks (see instrument_mops ()). */ + +struct bb_data +{ + int is_visited; + int has_sb; + const char *sb_file; + int sb_line_min; + int sb_line_max; +}; + +/* Memory access descriptor. */ + +struct mop_desc +{ + int is_call; + int is_store; + gimple_stmt_iterator gsi; + tree expr; +}; + +/* Descriptor of an ignore file entry. */ + +struct tsan_ignore_desc +{ + struct tsan_ignore_desc *next; + enum tsan_ignore_type type; + char *name; +}; + +/* Number of instrumented memory accesses in the current function. */ + +static int func_mops; + +/* Number of function calls in the current function. */ + +static int func_calls; + +/* Ignore status for the current function (see tsan_ignore_type). */ + +static enum tsan_ignore_type func_ignore; + +static int ignore_init = 0; +static struct tsan_ignore_desc *ignore_head; + +typedef struct mop_desc mop_desc; +DEF_VEC_O (mop_desc); +DEF_VEC_ALLOC_O (mop_desc, heap); +static VEC (mop_desc, heap) *mop_list; + +/* Returns a definition of a runtime variable with type TYP and name NAME. */ + +static tree +build_var_decl (tree typ, const char *name) +{ + tree id; + tree decl; + varpool_node_ptr var; + + /* Check if a user has defined it for testing. */ + id = get_identifier (name); + var = varpool_node_for_asm (id); + if (var != NULL) + { + decl = var->decl; + gcc_assert (TREE_CODE (decl) == VAR_DECL); + return decl; + } + + decl = build_decl (UNKNOWN_LOCATION, VAR_DECL, id, typ); + TREE_STATIC (decl) = 1; + TREE_PUBLIC (decl) = 1; + DECL_EXTERNAL (decl) = 1; + if (targetm.have_tls) + DECL_TLS_MODEL (decl) = decl_default_tls_model (decl); + TREE_USED (decl) = 1; + TREE_THIS_VOLATILE (decl) = 1; + SET_DECL_ASSEMBLER_NAME (decl, id); + return decl; +} + +/* Builds the following decl + extern __thread void **__tsan_shadow_stack; */ + +static tree +get_shadow_stack_decl (void) +{ + static tree decl; + + if (decl == NULL) + decl = build_var_decl (build_pointer_type (ptr_type_node), TSAN_STACK); + return decl; +} + +/* Builds the following decl + extern __thread int __tsan_thread_ignore; */ + +static tree +get_thread_ignore_decl (void) +{ + static tree decl; + + if (decl == NULL) + decl = build_var_decl (integer_type_node, TSAN_IGNORE); + return decl; +} + +/* Returns a definition of a runtime functione with type TYP and name NAME. */ + +static tree +build_func_decl (tree typ, const char *name) +{ + tree id; + cgraph_node_ptr func; + tree decl; + + /* Check if a user has defined it for testing. */ + id = get_identifier (name); + func = cgraph_node_for_asm (id); + if (func != NULL) + { + decl = func->decl; + gcc_assert (TREE_CODE (decl) == FUNCTION_DECL); + return decl; + } + + decl = build_fn_decl (name, typ); + TREE_NOTHROW (decl) = 1; + DECL_ATTRIBUTES (decl) = tree_cons (get_identifier ("leaf"), + NULL, DECL_ATTRIBUTES (decl)); + DECL_ASSEMBLER_NAME (decl); + return decl; +} + +/* Builds the following decl + void __tsan_handle_mop (void *addr, unsigned flags); */ + +static tree +get_handle_mop_decl (void) +{ + tree typ; + static tree decl; + + if (decl != NULL) + return decl; + + typ = build_function_type_list (void_type_node, ptr_type_node, + integer_type_node , NULL_TREE); + decl = build_func_decl (typ, TSAN_MOP); + return decl; +} + +/* Builds the following decl + void __tsan_init (void); */ + +static tree +get_init_decl (void) +{ + tree typ; + static tree decl; + + if (decl != NULL) + return decl; + + typ = build_function_type_list (void_type_node, NULL_TREE); + decl = build_func_decl (typ, TSAN_INIT); + return decl; +} + +/* Adds new ignore definition to the global list. + TYPE is the ignore type (see tsan_ignore_type). + NAME is the ignore pattern (e.g. "std*string*insert"). */ + +static void +ignore_append (enum tsan_ignore_type type, char *name) +{ + struct tsan_ignore_desc *desc; + + desc = XCNEW (struct tsan_ignore_desc); + desc->type = type; + desc->name = xstrdup (name); + desc->next = ignore_head; + ignore_head = desc; +} + +/* Checks as to whether identifier STR matches template TEMPL. + Templates can only contain '*', e.g. 'std*string*insert'. + Templates implicitly start and end with '*' + since they are matched against mangled names. + Returns non-zero if STR is matched against TEMPL. */ + +static int +ignore_match (char *templ, const char *str) +{ + char *tpos; + const char *spos; + + while (templ && templ [0]) + { + if (templ [0] == '*') + { + templ++; + continue; + } + if (str [0] == 0) + return 0; + tpos = strchr (templ, '*'); + if (tpos != NULL) + tpos [0] = 0; + spos = strstr (str, templ); + str = spos + strlen (templ); + templ = tpos; + if (tpos != NULL) + tpos [0] = '*'; + if (spos == NULL) + return 0; + } + return 1; +} + +/* Loads ignore definitions from the file specified by -ftsan-ignore=filename. + The result is stored in the global ignore_head list. + Ignore files have the following format: + +# This is a comment - ignored + +# The below line says to not instrument memory accesses +# in all functions that match 'std*string*insert' +fun:std*string*insert + +# The below line says to not instrument memory accesses +# in the function called 'foobar' *and* in all functions +# that it calls recursively +fun_r:foobar + +# The below line says to not create superblocks +# in the function called 'barbaz' +fun_hist:barbaz + +# Ignore all functions in the source file +src:atomic.c + +# Everything else is uninteresting for us (e.g. obj:) +*/ + +static void +ignore_load (void) +{ + FILE *f; + char *line; + size_t linesz; + ssize_t sz; + char buf [PATH_MAX]; + + if(getenv("GCCTSAN_PAUSE")) + { + int res; + printf("ATTACH A DEBUGGER AND PRESS ENTER\n"); + res = scanf("%s", buf); + (void)res; + } + + if (flag_tsan_ignore == NULL || flag_tsan_ignore [0] == 0) + return; + + f = fopen (flag_tsan_ignore, "r"); + if (f == NULL) + { + /* Try to open it relative to main_input_filename. */ + strncpy (buf, main_input_filename, sizeof (buf)); + buf [sizeof (buf) - 1] = 0; + line = strrchr (buf, '/'); + if (line != NULL) + { + line++; + strncpy (line, flag_tsan_ignore, sizeof (buf) - (line - buf)); + buf [sizeof (buf) - 1] = 0; + f = fopen (buf, "r"); + } + } + if (f == NULL) + { + error ("failed to open ignore file '%s'\n", flag_tsan_ignore); + return; + } + + line = 0; + linesz = 0; + while ((sz = getline (&line, &linesz, f)) != -1) + { + if (sz == 0) + continue; + /* Strip line terminator. */ + if (line [sz - 1] == '\r' || line [sz - 1] == '\n') + line [sz - 1] = 0; + if (strncmp (line, "src:", sizeof ("src:") - 1) == 0) + ignore_append (tsan_ignore_func, line + sizeof ("src:") - 1); + else if (strncmp (line, "fun:", sizeof ("fun:") - 1) == 0) + ignore_append (tsan_ignore_mop, line + sizeof ("fun:") - 1); + else if (strncmp (line, "fun_r:", sizeof ("fun_r:") - 1) == 0) + ignore_append (tsan_ignore_rec, line + sizeof ("fun_r:") - 1); + else if (strncmp (line, "fun_hist:", sizeof ("fun_hist:") - 1) == 0) + ignore_append (tsan_ignore_hist, line + sizeof ("fun_hist:") - 1); + /* Other lines are not interesting. */ + } + + free (line); + fclose (f); +} + +/* Returns ignore status for the current function. */ + +static enum tsan_ignore_type +tsan_ignore (void) +{ + const char *func_name; + const char *src_name; + struct tsan_ignore_desc *desc; + + if (ignore_init == 0) + { + ignore_load (); + ignore_init = 1; + } + + src_name = expand_location (cfun->function_start_locus).file; + if (src_name == NULL) + src_name = ""; + + func_name = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (cfun->decl)); + /* Ignore all functions starting with __tsan_ - intended for testing. */ + if (strncmp (func_name, TSAN_PERFIX, sizeof (TSAN_PERFIX) - 1) == 0) + return tsan_ignore_func; + + /* Ignore global ctors. */ + if (strncmp (func_name, "_GLOBAL", sizeof ("_GLOBAL") - 1) == 0) + return tsan_ignore_func; + + for (desc = ignore_head; desc; desc = desc->next) + { + if (desc->type == tsan_ignore_func) + { + if (ignore_match (desc->name, src_name)) + return desc->type; + } + else if (ignore_match (desc->name, func_name)) + return desc->type; + } + return tsan_ignore_none; +} + +/* Builds either (__tsan_shadow_stack += 1) or (__tsan_shadow_stack -= 1) + expression depending on DO_DEC parameter. Appends the result to SEQ. */ + +static void +build_stack_op (gimple_seq *seq, bool do_dec) +{ + tree op_size; + double_int op_size_cst; + unsigned long long size_val; + unsigned long long size_valhi; + tree op_expr; + gimple assign; + tree sstack_decl; + gimple_seq s; + + op_size = TYPE_SIZE (ptr_type_node); + op_size_cst = tree_to_double_int (op_size); + size_val = op_size_cst.low / BITS_PER_UNIT; + size_valhi = 0; + if (do_dec) + { + size_val = -size_val; + size_valhi = -1; + } + op_size = build_int_cst_wide (sizetype, size_val, size_valhi); + sstack_decl = get_shadow_stack_decl (); + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, sstack_decl, op_size); + + s = NULL; + op_expr = force_gimple_operand (op_expr, &s, true, NULL_TREE); + gimple_seq_add_seq (seq, s); + + assign = gimple_build_assign (sstack_decl, op_expr); + gimple_seq_add_stmt (seq, assign); +} + +/* Builds either (__tsan_thread_ignore += 1) or (__tsan_thread_ignore -= 1) + expression depending on OP parameter. Stores the result in SEQ. */ + +static void +build_rec_ignore_op (gimple_seq *seq, enum tree_code op) +{ + tree rec_expr; + gimple_seq rec_inc; + gimple rec_assign; + tree ignore_decl; + + ignore_decl = get_thread_ignore_decl (); + rec_expr = build2 (op, integer_type_node, ignore_decl, integer_one_node); + rec_inc = NULL; + rec_expr = force_gimple_operand (rec_expr, &rec_inc, true, NULL_TREE); + gimple_seq_add_seq (seq, rec_inc); + rec_assign = gimple_build_assign (ignore_decl, rec_expr); + gimple_seq_add_stmt (seq, rec_assign); +} + +/* Build the following gimple sequence: + __tsan_shadow_stack [-1] = __builtin_return_address (0); + Stores the result in SEQ. */ + +static void +build_stack_assign (gimple_seq *seq) +{ + tree pc_addr; + tree op_size; + tree op_expr; + tree stack_op; + tree retaddr_decl; + tree assign; + + retaddr_decl = implicit_built_in_decls [BUILT_IN_RETURN_ADDRESS]; + pc_addr = build_call_expr (retaddr_decl, 1, integer_zero_node); + op_size = build_int_cst_wide (sizetype, -(POINTER_SIZE / BITS_PER_UNIT), -1); + op_expr = build2 (POINTER_PLUS_EXPR, ptr_type_node, + get_shadow_stack_decl (), op_size); + stack_op = build1 (INDIRECT_REF, ptr_type_node, op_expr); + assign = build2 (MODIFY_EXPR, ptr_type_node, stack_op, pc_addr); + force_gimple_operand (assign, seq, true, NULL_TREE); +} + +/* Builds the following gimple sequence: + __tsan_handle_mop (&EXPR, + (IS_SBLOCK | (IS_STORE << 1) | ((sizeof (EXPR) - 1) << 2); + The result is stored in GSEQ. */ + +static void +instr_mop (tree expr, int is_store, int is_sblock, gimple_seq *gseq) +{ + tree addr_expr; + tree expr_type; + unsigned size; + unsigned flags; + tree flags_expr; + tree call_expr; + + gcc_assert (gseq != 0 && *gseq == 0); + gcc_assert (is_gimple_addressable (expr)); + + addr_expr = build_addr (unshare_expr (expr), current_function_decl); + expr_type = TREE_TYPE (expr); + while (TREE_CODE (expr_type) == ARRAY_TYPE) + expr_type = TREE_TYPE (expr_type); + size = TREE_INT_CST_LOW (TYPE_SIZE (expr_type)); + size = size / BITS_PER_UNIT; + if (size > MAX_MOP_BYTES) + size = MAX_MOP_BYTES; + size -= 1; + flags = ((!!is_sblock << 0) + (!!is_store << 1) + (size << 2)); + flags_expr = build_int_cst (unsigned_type_node, flags); + call_expr = build_call_expr (get_handle_mop_decl (), + 2, addr_expr, flags_expr); + force_gimple_operand (call_expr, gseq, true, 0); +} + +/* Builds the following gimple sequence: + int is_store = (EXPR != RHS); // The temp is not actually introduced. + __tsan_handle_mop (&EXPR, + (IS_SBLOCK | (IS_STORE << 1) | ((sizeof (EXPR) - 1) << 2); + The result is stored in GSEQ. */ + +static void +instr_vptr_store (tree expr, tree rhs, int is_sblock, gimple_seq *gseq) +{ + tree expr_ptr; + tree addr_expr; + tree expr_type; + tree expr_size; + double_int size; + unsigned flags; + tree flags_expr; + gimple_seq flags_seq; + gimple collect; + tree is_store_expr; + + expr_ptr = build_addr (unshare_expr (expr), current_function_decl); + addr_expr = force_gimple_operand (expr_ptr, gseq, true, NULL_TREE); + expr_type = TREE_TYPE (expr); + while (TREE_CODE (expr_type) == ARRAY_TYPE) + expr_type = TREE_TYPE (expr_type); + expr_size = TYPE_SIZE (expr_type); + size = tree_to_double_int (expr_size); + gcc_assert (size.high == 0 && size.low != 0); + if (size.low > 128) + size.low = 128; + size.low = (size.low / 8) - 1; + flags = ((!!is_sblock << 0) + (size.low << 2)); + flags_expr = build_int_cst (unsigned_type_node, flags); + is_store_expr = build2 (NE_EXPR, unsigned_type_node, + build1 (VIEW_CONVERT_EXPR, size_type_node, expr), + build1 (VIEW_CONVERT_EXPR, size_type_node, rhs)); + is_store_expr = build2 (LSHIFT_EXPR, unsigned_type_node, + is_store_expr, integer_one_node); + flags_expr = build2 (BIT_IOR_EXPR, unsigned_type_node, + is_store_expr, flags_expr); + flags_seq = 0; + flags_expr = force_gimple_operand (flags_expr, &flags_seq, true, NULL_TREE); + gimple_seq_add_seq (gseq, flags_seq); + collect = gimple_build_call ( + get_handle_mop_decl (), 2, addr_expr, flags_expr); + gimple_seq_add_stmt (gseq, collect); +} + +/* Returns true if function entry and exit need to be instrumented. */ + +static bool +is_func_instrumentation_required (void) +{ + if (func_calls == 0 && func_mops == 0) + return false; + if (func_ignore != tsan_ignore_rec) + return true; + if (func_ignore == tsan_ignore_rec && func_calls != 0) + return true; + return false; +} + +/* Returns gimple seq that needs to be inserted at function entry. */ + +static gimple_seq +build_func_entry_instr (void) +{ + gimple_seq gs; + + gs = NULL; + gcc_assert (is_func_instrumentation_required ()); + if (func_ignore != tsan_ignore_rec) + { + build_stack_assign (&gs); + build_stack_op (&gs, false); + } + else + build_rec_ignore_op (&gs, PLUS_EXPR); + return gs; +} + +/* Returns gimple seq that needs to be inserted before function exit. */ + +static gimple_seq +build_func_exit_instr (void) +{ + gimple_seq gs; + + gs = NULL; + gcc_assert (is_func_instrumentation_required ()); + if (func_ignore != tsan_ignore_rec) + build_stack_op (&gs, true); + else + build_rec_ignore_op (&gs, MINUS_EXPR); + return gs; +} + +/* Sets location LOC for all gimples in the SEQ. */ + +static void +set_location (gimple_seq seq, location_t loc) +{ + gimple_seq_node n; + + for (n = gimple_seq_first (seq); n != NULL; n = n->next) + gimple_set_location (n->stmt, loc); + verify_gimple_in_seq (seq); +} + +/* Check as to whether EXPR refers to a store to vptr. */ + +static tree +is_vptr_store (gimple stmt, tree expr, int is_store) +{ + if (is_store == 1 + && gimple_assign_single_p (stmt) + && TREE_CODE (expr) == COMPONENT_REF) + { + tree field = TREE_OPERAND (expr, 1); + if (TREE_CODE (field) == FIELD_DECL + && DECL_VIRTUAL_P (field)) + return gimple_assign_rhs1 (stmt); + } + return NULL; +} + +/* Checks as to whether EXPR refers to constant var/field/param. + Don't bother to instrument them. */ + +static int +is_load_of_const (tree expr, int is_store) +{ + if (is_store) + return 0; + if (TREE_CODE (expr) == COMPONENT_REF) + expr = TREE_OPERAND (expr, 1); + if (TREE_CODE (expr) == VAR_DECL + || TREE_CODE (expr) == PARM_DECL + || TREE_CODE (expr) == FIELD_DECL) + { + if (TREE_READONLY (expr)) + return 1; + } + return 0; +} + +/* Checks as to whether EXPR needs to be instrumented, + if so puts it into the MOP_LIST. + GSI is the iterator from which EXPR was extracted. + IS_STORE says as to whether EXPR refers to a memory store + or a memory load. */ + +static void +handle_expr (gimple_stmt_iterator gsi, tree expr, int is_store, + VEC (mop_desc, heap) **mop_list) +{ + enum tree_code tcode; + struct mop_desc mop; + unsigned fld_off; + unsigned fld_size; + + tcode = TREE_CODE (expr); + + /* Below are things we do not instrument + (no possibility of races or not implemented yet). */ + if ((func_ignore & (tsan_ignore_mop | tsan_ignore_rec)) + || get_base_address (expr) == NULL + /* Compiler-emitted artificial variables. */ + || (DECL_P (expr) && DECL_ARTIFICIAL (expr)) + /* The var does not live in memory -> no possibility of races. */ + || (tcode == VAR_DECL + && TREE_ADDRESSABLE (expr) == 0 + && DECL_EXTERNAL (expr) == 0) + /* TODO (dvyukov): not implemented. */ + || TREE_CODE (TREE_TYPE (expr)) == RECORD_TYPE + /* TODO (dvyukov): not implemented. */ + || tcode == CONSTRUCTOR + /* TODO (dvyukov): not implemented. */ + || tcode == PARM_DECL + /* Load of a const variable/parameter/field. */ + || is_load_of_const (expr, is_store)) + return; + + if (tcode == COMPONENT_REF) + { + tree field = TREE_OPERAND (expr, 1); + if (TREE_CODE (field) == FIELD_DECL) + { + fld_off = TREE_INT_CST_LOW (DECL_FIELD_BIT_OFFSET (field)); + fld_size = TREE_INT_CST_LOW (DECL_SIZE (field)); + if (((fld_off % BITS_PER_UNIT) != 0) + || ((fld_size % BITS_PER_UNIT) != 0)) + { + /* As of now it crashes compilation. + TODO (dvyukov): handle bit-fields as if touching + the whole field. */ + return; + } + } + } + + /* TODO (dvyukov): handle other cases + (FIELD_DECL, MEM_REF, ARRAY_RANGE_REF, TARGET_MEM_REF, ADDR_EXPR). */ + if (tcode != ARRAY_REF + && tcode != VAR_DECL + && tcode != COMPONENT_REF + && tcode != INDIRECT_REF + && tcode != MEM_REF) + return; + + mop.is_call = 0; + mop.gsi = gsi; + mop.expr = expr; + mop.is_store = is_store; + VEC_safe_push (mop_desc, heap, *mop_list, &mop); +} + +/* Collects all interesting memory accesses from the gimple pointed to by GSI + into MOP_LIST. */ + +static void +handle_gimple (gimple_stmt_iterator gsi, VEC (mop_desc, heap) **mop_list) +{ + unsigned i; + struct mop_desc mop; + gimple stmt; + enum gimple_code gcode; + tree rhs; + tree lhs; + + stmt = gsi_stmt (gsi); + gcode = gimple_code (stmt); + if (gcode >= LAST_AND_UNUSED_GIMPLE_CODE) + return; + + switch (gcode) + { + case GIMPLE_CALL: + { + func_calls += 1; + memset (&mop, 0, sizeof (mop)); + mop.is_call = 1; + VEC_safe_push (mop_desc, heap, *mop_list, &mop); + break; + } + + case GIMPLE_ASSIGN: + { + /* Handle assignment lhs as store. */ + lhs = gimple_assign_lhs (stmt); + handle_expr (gsi, lhs, 1, mop_list); + + /* Handle operands as loads. */ + for (i = 1; i < gimple_num_ops (stmt); i++) + { + rhs = gimple_op (stmt, i); + handle_expr (gsi, rhs, 0, mop_list); + } + break; + } + + default: + break; + } +} + +/* Instruments single basic block BB. + BBD is the sblock info associated with the block. */ + +static void +instrument_bblock (struct bb_data *bbd, basic_block bb) +{ + int ix; + int is_sblock; + gimple_stmt_iterator gsi; + struct mop_desc *mop; + gimple stmt; + location_t loc; + expanded_location eloc; + gimple_seq instr_seq; + tree rhs; + + /* Iterate over all gimples and collect interesting mops into mop_list. */ + VEC_free (mop_desc, heap, mop_list); + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + handle_gimple (gsi, &mop_list); + } + + mop = 0; + for (ix = 0; VEC_iterate (mop_desc, mop_list, ix, mop); ix += 1) + { + if (mop->is_call != 0) + { + /* After a function call we must start a brand new sblock, + because the function can contain synchronization. */ + bbd->has_sb = 0; + continue; + } + + func_mops += 1; + stmt = gsi_stmt (mop->gsi); + loc = gimple_location (stmt); + eloc = expand_location (loc); + + /* Check as to whether we may not set sblock flag for the access. */ + is_sblock = (bbd->has_sb == 0 + || !(eloc.file != 0 + && bbd->sb_file != 0 + && strcmp (eloc.file, bbd->sb_file) == 0 + && eloc.line >= bbd->sb_line_min + && eloc.line <= bbd->sb_line_max)); + + if (func_ignore == tsan_ignore_hist) + is_sblock = 0; + + if (is_sblock) + { + /* Start new sblock with new source info. */ + bbd->has_sb = 1; + bbd->sb_file = eloc.file; + bbd->sb_line_min = eloc.line; + bbd->sb_line_max = eloc.line + SBLOCK_SIZE; + } + + instr_seq = 0; + rhs = is_vptr_store (stmt, mop->expr, mop->is_store); + if (rhs == NULL) + instr_mop (mop->expr, mop->is_store, is_sblock, &instr_seq); + else + instr_vptr_store (mop->expr, rhs, is_sblock, &instr_seq); + gcc_assert (instr_seq != 0); + set_location (instr_seq, loc); + /* Instrumentation for assignment of a function result + must be inserted after the call. Instrumentation for + reads of function arguments must be inserted before the call. + That's because the call can contain synchronization. */ + if (is_gimple_call (stmt) && mop->is_store == 1) + gsi_insert_seq_after (&mop->gsi, instr_seq, GSI_NEW_STMT); + else + gsi_insert_seq_before (&mop->gsi, instr_seq, GSI_SAME_STMT); + } +} + +/* Instruments all interesting memory accesses in the current function. */ + +static void +instrument_mops (void) +{ + basic_block bb; + int *blocks_inverted; + struct bb_data *bb_data; + struct bb_data *pred; + struct bb_data *bbd; + edge e; + edge_iterator ei; + int sb_line_min, sb_line_max; + int cnt, i; + + /* The function does basic block traversal in reverse top sort order + of the inverted CFG. Such order is required to properly mark super-blocks. + The idea behind super-blocks is as follows. + If several memory accesses happen within SBLOCK_SIZE source code lines + from each other, then we only mark the first access as SBLOCK. + This allows the runtime library to memorize a stack trace + only for the first access and do not memorize for others. + This significantly reduces memory consumption in exchange for slightly + imprecise stack traces for previous accesses. */ + + blocks_inverted = XNEWVEC (int, last_basic_block + NUM_FIXED_BLOCKS); + bb_data = XCNEWVEC (struct bb_data, last_basic_block + NUM_FIXED_BLOCKS); + cnt = inverted_post_order_compute (blocks_inverted); + for (i = 0; i < cnt; i++) + { + bb = BASIC_BLOCK (blocks_inverted [i]); + bbd = &bb_data [bb->index]; + /* Iterate over all predecessors and merge their sblock info. */ + FOR_EACH_EDGE (e, ei, bb->preds) + { + pred = &bb_data [e->src->index]; + if (!pred->is_visited || !pred->has_sb || pred == bbd) + { + /* If there is a not visited predecessor, + or a predecessor with no active sblock info, + or a self-loop, then we will have to start + a brand new sblock on next memory access. */ + bbd->has_sb = 0; + break; + } + else if (bbd->has_sb == 0) + { + /* If it's a first predecessor, just copy the info. */ + bbd->has_sb = 1; + bbd->sb_file = pred->sb_file; + bbd->sb_line_min = pred->sb_line_min; + bbd->sb_line_max = pred->sb_line_max; + } + else + { + /* Otherwise, find the interception + between two sblock descriptors. */ + bbd->has_sb = 0; + if (bbd->sb_file != 0 && pred->sb_file != 0 + && strcmp (bbd->sb_file, pred->sb_file) == 0) + { + sb_line_min = MAX (bbd->sb_line_min, pred->sb_line_min); + sb_line_max = MIN (bbd->sb_line_max, pred->sb_line_max); + if (sb_line_min <= sb_line_max) + { + bbd->has_sb = 1; + bbd->sb_line_min = sb_line_min; + bbd->sb_line_max = sb_line_max; + } + } + /* No interception, have to start new sblock. */ + if (bbd->has_sb == 0) + break; + } + } + + instrument_bblock (bbd, bb); + bbd->is_visited = 1; + } + + free (blocks_inverted); + free (bb_data); +} + +/* Instruments function entry. */ + +static void +instrument_func_entry (void) +{ + gimple_seq seq; + basic_block entry_bb; + edge entry_edge; + gimple_stmt_iterator gsi; + + /* Insert new BB before the first BB. */ + seq = build_func_entry_instr (); + gcc_assert (seq != NULL); + entry_bb = ENTRY_BLOCK_PTR; + entry_edge = single_succ_edge (entry_bb); + set_location (seq, cfun->function_start_locus); + entry_bb = split_edge (entry_edge); + gsi = gsi_start_bb (entry_bb); + gsi_insert_seq_after (&gsi, seq, GSI_NEW_STMT); +} + +/* Instruments function exits. */ + +static void +instrument_func_exit (void) +{ + location_t loc; + gimple_seq seq; + basic_block exit_bb; + gimple_stmt_iterator gsi; + gimple stmt; + edge e; + edge_iterator ei; + + /* Find all function exits. */ + exit_bb = EXIT_BLOCK_PTR; + FOR_EACH_EDGE (e, ei, exit_bb->preds) + { + gsi = gsi_last_bb (e->src); + stmt = gsi_stmt (gsi); + gcc_assert (gimple_code (stmt) == GIMPLE_RETURN); + loc = gimple_location (stmt); + seq = build_func_exit_instr (); + gcc_assert (seq != NULL); + set_location (seq, loc); + gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT); + } +} + +/* ThreadSanitizer instrumentation pass. */ + +static unsigned +tsan_pass (void) +{ + struct gimplify_ctx gctx; + + func_ignore = tsan_ignore (); + if (func_ignore == tsan_ignore_func) + return 0; + + func_calls = 0; + func_mops = 0; + + push_gimplify_context (&gctx); + + instrument_mops (); + + if (is_func_instrumentation_required ()) + { + instrument_func_entry (); + instrument_func_exit (); + } + + pop_gimplify_context (NULL); + + return 0; +} + +/* The pass's gate. */ + +static bool +tsan_gate (void) +{ + return flag_tsan != 0; +} + +/* Inserts __tsan_init () into the list of CTORs. */ + +void tsan_finish_file (void) +{ + tree ctor_statements; + + ctor_statements = NULL_TREE; + append_to_statement_list (build_call_expr (get_init_decl (), 0), + &ctor_statements); + cgraph_build_static_cdtor ('I', ctor_statements, + MAX_RESERVED_INIT_PRIORITY - 1); +} + +/* The pass descriptor. */ + +struct gimple_opt_pass pass_tsan = {{ + GIMPLE_PASS, + "tsan", /* name */ + tsan_gate, /* gate */ + tsan_pass, /* execute */ + NULL, /* sub */ + NULL, /* next */ + 0, /* static_pass_number */ + TV_NONE, /* tv_id */ + PROP_trees | PROP_cfg, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + TODO_dump_cgraph | TODO_dump_func | TODO_verify_all + | TODO_update_ssa | TODO_update_address_taken /* todo_flags_finish */ +}}; + Property changes on: gcc/tree-tsan.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/tree-tsan.h =================================================================== --- gcc/tree-tsan.h (revision 0) +++ gcc/tree-tsan.h (revision 0) @@ -0,0 +1,26 @@ +/* ThreadSanitizer, a data race detector. + Copyright (C) 2011 Free Software Foundation, Inc. + Contributed by Dmitry Vyukov <dvyukov@google.com> + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef TREE_TSAN +#define TREE_TSAN + +extern void tsan_finish_file (void); + +#endif /* TREE_TSAN */ Property changes on: gcc/tree-tsan.h ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/tree-pass.h =================================================================== --- gcc/tree-pass.h (revision 181347) +++ gcc/tree-pass.h (working copy) @@ -353,6 +353,7 @@ extern struct gimple_opt_pass pass_mudflap_1; extern struct gimple_opt_pass pass_mudflap_2; extern struct gimple_opt_pass pass_asan; +extern struct gimple_opt_pass pass_tsan; extern struct gimple_opt_pass pass_lower_cf; extern struct gimple_opt_pass pass_refactor_eh; extern struct gimple_opt_pass pass_lower_eh; Index: gcc/toplev.c =================================================================== --- gcc/toplev.c (revision 181347) +++ gcc/toplev.c (working copy) @@ -75,6 +75,7 @@ #include "alloc-pool.h" #include "tree-mudflap.h" #include "tree-asan.h" +#include "tree-tsan.h" #include "tree-pass.h" #include "gimple.h" #include "tree-ssa-alias.h" @@ -620,6 +621,10 @@ if (flag_asan) asan_finish_file (); + /* File-scope initialization for ThreadSanitizer. */ + if (flag_tsan) + tsan_finish_file (); + output_shared_constant_pool (); output_object_blocks (); Index: gcc/testsuite/gcc.dg/tsan-ignore.ignore =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.ignore (revision 0) @@ -0,0 +1,7 @@ +#comment +fun:foo +fun:*bar +fun:baz* +fun:*bla* +fun:x*x +src:tsan-ignore.h Index: gcc/testsuite/gcc.dg/tsan.h =================================================================== --- gcc/testsuite/gcc.dg/tsan.h (revision 0) +++ gcc/testsuite/gcc.dg/tsan.h (revision 0) @@ -0,0 +1,101 @@ +/* Helper declarations and functions for ThreadSanitizer instrumentation (-ftsan) testing */ + +int printf (char *str, ...); +void exit (int); + +/* Variables referenced by the instrumentation. */ + +__thread void **__tsan_shadow_stack; +__thread int __tsan_thread_ignore; + +/* Local helper vars. */ + +__thread void *shadow_stack[1024]; +__thread int pad[1024]; +__thread int mop_expect = 0; +__thread int mop_depth = 0; +__thread void* mop_addr = 0; +__thread unsigned long long mop_pc = 0; +__thread unsigned mop_flags = 0; +__thread unsigned mop_line = 0; + +/* Setups shadow stack var (not instrumented). */ + +void +__tsan_init (void) +{ + __tsan_shadow_stack = shadow_stack; +} + +/* Declare that we expect an instrumented memory access (not instrumented). + depth - stack depth of the mop (0 - main, 1 - func called from main and so on). + addr - memory access address. + is_store - store/load. + is_sblock - superblock flag of the access. + size - memory access size in bytes. */ + +void +__tsan_expect_mop (int depth, void const volatile *addr, int is_store, + int is_sblock, int size, unsigned line) +{ + if (mop_expect) + { + printf ("missed mop: addr=%p pc=%d line=%d\n", mop_addr, mop_pc, mop_line); + exit (1); + } + + mop_expect = 1; + mop_depth = depth; + mop_addr = (void*)addr; + mop_pc = (unsigned long long)__builtin_return_address(0); + mop_flags = !!is_sblock | (!!is_store << 1) | ((size - 1) << 2); + mop_line = line; +} + +/* Memory access function (referenced by instrumentation, not instrumented). */ + +void +__tsan_handle_mop (void *addr, unsigned flags) +{ + unsigned long long pc; + int depth; + + printf ("mop: addr=%p flags=%x called from %p line=%d\n", + addr, flags, __tsan_shadow_stack [-2], mop_line); + if (mop_expect == 0) + return; + + /* Verify parameters with what we expect. */ + + if (addr != mop_addr) + { + printf ("incorrect mop addr: %p/%p line=%d\n", + addr, mop_addr, mop_line); + exit (1); + } + + pc = (unsigned long long)__builtin_return_address(0); + if (pc < mop_pc - 100 || pc > mop_pc + 100) + { + printf ("incorrect mop pc: %p/%p line=%d\n", + (void*)pc, (void*)mop_pc, mop_line); + exit (1); + } + + depth = __tsan_shadow_stack - shadow_stack - 1; + if (depth != mop_depth) + { + printf ("incorrect mop depth: %d/%d line=%d\n", + depth, mop_depth, mop_line); + exit (1); + } + + if (flags != mop_flags) + { + printf ("incorrect mop flags: %x/%x line=%d\n", + flags, mop_flags, mop_line); + exit (1); + } + + mop_expect = 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan.h ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-ignore.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.c (revision 0) @@ -0,0 +1,49 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan -O1 -ftsan-ignore=tsan-ignore.ignore" } */ +#include "tsan.h" +#include "tsan-ignore.h" + +/* Check ignore file handling. */ + +int +foo (int *p) +{ + p [0] = 1; +} + +int bar (int *p) +{ + p [0] = 1; +} + +int baz (int *p) +{ + p [0] = 1; +} + +int bla (int *p) +{ + p [0] = 1; +} + +int xxx (int *p) +{ + p [0] = 1; +} + +int +main (void) +{ + int p, x; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + /* All these functions must be ignored. */ + foo (&x); + bar (&x); + baz (&x); + bla (&x); + xxx (&x); + in_tsan_ignore_header (&x); + p = 0; + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-ignore.h =================================================================== --- gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) +++ gcc/testsuite/gcc.dg/tsan-ignore.h (revision 0) @@ -0,0 +1,5 @@ +int +in_tsan_ignore_header (int *p) +{ + p [0] = 1; +} Property changes on: gcc/testsuite/gcc.dg/tsan-ignore.h ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-stack.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-stack.c (revision 0) @@ -0,0 +1,34 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan -O1" } */ +#include "tsan.h" + +/* Check shadow stack maintance. */ + +void barbaz (int volatile *p) +{ + __tsan_expect_mop(2, p, 1, 1, sizeof(*p), __LINE__); + *p = 11; +} + +void +foobar (int volatile *p) +{ + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + p[0] = 13; + barbaz (p); + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + p[0] = 17; +} + +int +main (void) +{ + int volatile p; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + p = 0; + foobar (&p); + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + p = 1; + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-stack.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/testsuite/gcc.dg/tsan-mop.c =================================================================== --- gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) +++ gcc/testsuite/gcc.dg/tsan-mop.c (revision 0) @@ -0,0 +1,93 @@ +/* { dg-do run } */ +/* { dg-options "-ftsan -O1" } */ +#include "tsan.h" + +/* Sanity check for memory accesses instrumentation. */ + +typedef struct C C; +struct C +{ + int r, g, b; +}; + +typedef struct P P; +struct P +{ + int x, y; + C c; + C *cp; + C ca[2]; +}; + +void teststructp (P *p) +{ + int volatile tmp; + + __tsan_expect_mop(2, &p->x, 1, 1, sizeof(p->x), __LINE__); + p->x = 42; + + __tsan_expect_mop(2, &p->x, 0, 1, sizeof(p->x), __LINE__); + tmp = p->x; + +} + +void testfor (P *p) +{ + long long volatile tmp = 0; + long long *a = (long long *)p->ca; + int i; + for (i = 0; i < 2; i++) + { + __tsan_expect_mop(2, &a [i], 0, 1, sizeof(a [0]), __LINE__); + tmp += a [i]; + } +} + +void +teststruct (void) +{ + P p; + int volatile tmp; + + __tsan_expect_mop(1, &p.x, 1, 1, sizeof(p.x), __LINE__); + p.x = 42; + + __tsan_expect_mop(1, &p.x, 0, 1, sizeof(p.x), __LINE__); + tmp = p.x; + + __tsan_expect_mop(1, &p.cp, 1, 1, sizeof(p.cp), __LINE__); + p.cp = &p.c; + + __tsan_expect_mop(1, &p.c.r, 1, 1, sizeof(p.c.r), __LINE__); + p.c.r = 11; + + teststructp (&p); + testfor (&p); +} + +void +foobar (int *p) +{ + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + p[0] = 1; + + __tsan_expect_mop(1, p, 1, 1, sizeof(*p), __LINE__); + *p = 2; + + __tsan_expect_mop(1, (char*)p+3, 1, 1, 1, __LINE__); + *((char*)p+3) = 3; +} + +int +main (void) +{ + int p; + + __tsan_expect_mop(0, &p, 1, 1, sizeof(p), __LINE__); + p = 0; + foobar (&p); + + teststruct (); + + return 0; +} Property changes on: gcc/testsuite/gcc.dg/tsan-mop.c ___________________________________________________________________ Added: svn:eol-style + LF Index: gcc/common.opt =================================================================== --- gcc/common.opt (revision 181347) +++ gcc/common.opt (working copy) @@ -1551,6 +1551,14 @@ Common RejectNegative Report Var(flag_mudflap_ignore_reads) Ignore read operations when inserting mudflap instrumentation +ftsan +Common RejectNegative Report Var(flag_tsan) +Add ThreadSanitizer instrumentation + +ftsan-ignore= +Common RejectNegative Joined Var(flag_tsan_ignore) +-ftsan-ignore=filename ThreadSanitizer ignore file + fdce Common Var(flag_dce) Init(1) Optimization Use the RTL dead code elimination pass Index: gcc/Makefile.in =================================================================== --- gcc/Makefile.in (revision 181347) +++ gcc/Makefile.in (working copy) @@ -1495,6 +1495,7 @@ tree-streamer-out.o \ tree-tailcall.o \ tree-threadsafe-analyze.o \ + tree-tsan.o \ tree-vect-generic.o \ tree-vect-patterns.o \ tree-vect-data-refs.o \ @@ -2819,6 +2820,12 @@ $(C_TREE_H) $(C_COMMON_H) $(GIMPLE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) \ output.h langhooks.h tree-mudflap.h $(TM_H) coretypes.h \ $(GGC_H) gt-tree-mudflap.h $(TREE_PASS_H) $(DIAGNOSTIC_CORE_H) +tree-tsan.o : $(CONFIG_H) $(SYSTEM_H) $(TREE_H) $(TREE_INLINE_H) \ + $(GIMPLE_H) $(DIAGNOSTIC_H) langhooks.h \ + $(TM_H) coretypes.h $(TREE_DUMP_H) $(TREE_PASS_H) $(CGRAPH_H) $(GGC_H) \ + $(BASIC_BLOCK_H) $(FLAGS_H) $(FUNCTION_H) \ + $(TM_P_H) $(TREE_FLOW_H) $(DIAGNOSTIC_CORE_H) $(GIMPLE_H) tree-iterator.h \ + intl.h cfghooks.h output.h options.h c-family/c-common.h tree-tsan.h tree-pretty-print.o : tree-pretty-print.c $(CONFIG_H) $(SYSTEM_H) \ $(TREE_H) $(DIAGNOSTIC_H) $(HASHTAB_H) $(TREE_FLOW_H) \ $(TM_H) coretypes.h tree-iterator.h $(SCEV_H) langhooks.h \ @@ -2871,7 +2878,7 @@ $(OPTS_H) params.def tree-mudflap.h $(TREE_PASS_H) $(GIMPLE_H) \ tree-ssa-alias.h $(PLUGIN_H) realmpfr.h tree-diagnostic.h \ tree-pretty-print.h opts-diagnostic.h $(COMMON_TARGET_H) \ - tree-threadsafe-analyze.h + tree-threadsafe-analyze.h tree-tsan.h hwint.o : hwint.c $(CONFIG_H) $(SYSTEM_H) $(DIAGNOSTIC_CORE_H) Index: gcc/passes.c =================================================================== --- gcc/passes.c (revision 181347) +++ gcc/passes.c (working copy) @@ -1345,6 +1345,7 @@ NEXT_PASS (pass_pre); NEXT_PASS (pass_sink_code); NEXT_PASS (pass_asan); + NEXT_PASS (pass_tsan); NEXT_PASS (pass_tree_loop); { struct opt_pass **p = &pass_tree_loop.pass.sub; -- This patch is available for review at http://codereview.appspot.com/5303083
Sign in to reply to this message.
On 2011/11/10 23:56:59, davidxl wrote: > http://codereview.appspot.com/5303083/diff/28001/gcc/tree-tsan.c > File gcc/tree-tsan.c (right): > > http://codereview.appspot.com/5303083/diff/28001/gcc/tree-tsan.c#newcode227 > gcc/tree-tsan.c:227: var = varpool_node_for_asm (id); > Use cgraph_node_for_asm instead. Done. I've also updated he repo and added per-translation-unit ctors (see tsan_finish_file ()) like in asan. PTAL.
Sign in to reply to this message.
On 2011/11/11 00:00:35, davidxl wrote: > Have you run through SPEC, and SPEC06 with this change? What is the > instrumentation overhead using gcc? > > David No, I don't use SPEC for testing and benchmarking. For testing I use a pair of large tests in you-know-what codebase. As for benchmarking, overheads are dominated by runtime processing, moderate variations in instrumentation have negligible effect. As of now I would be happy to make it correct and finally commit, probably I spent a way too much time on optimizations already so now it's hard to make it correct and commit.
Sign in to reply to this message.
Ok for google/main after compiler bootstrap and regression test (without ftsan), and some large tests with tsan turned on (as many as you can but at your discretion). David On Sun, Nov 13, 2011 at 11:59 PM, <dvyukov@google.com> wrote: > On 2011/11/11 00:00:35, davidxl wrote: >> >> Have you run through SPEC, and SPEC06 with this change? What is the >> instrumentation overhead using gcc? > >> David > > No, I don't use SPEC for testing and benchmarking. For testing I use a > pair of large tests in you-know-what codebase. As for benchmarking, > overheads are dominated by runtime processing, moderate variations in > instrumentation have negligible effect. As of now I would be happy to > make it correct and finally commit, probably I spent a way too much time > on optimizations already so now it's hard to make it correct and commit. > > > http://codereview.appspot.com/5303083/ >
Sign in to reply to this message.
On 2011/11/14 16:48:45, davidxl wrote: > Ok for google/main after compiler bootstrap and regression test > (without ftsan), and some large tests with tsan turned on (as many as > you can but at your discretion). Hi David, Perhaps a bit late question... but better late than never. I've added some unit tests, and I only tested them on Linux/x86 (and it is the only platform that we currently care about). AFAIR I've seen some sort of annotations to restrict tests to particular platforms. Does it make sense to add them to the tests?
Sign in to reply to this message.
On 11-11-30 03:53 , dvyukov@google.com wrote: > On 2011/11/14 16:48:45, davidxl wrote: >> Ok for google/main after compiler bootstrap and regression test >> (without ftsan), and some large tests with tsan turned on (as many as >> you can but at your discretion). > > Hi David, > > Perhaps a bit late question... but better late than never. > I've added some unit tests, and I only tested them on Linux/x86 (and it > is the only platform that we currently care about). AFAIR I've seen some > sort of annotations to restrict tests to particular platforms. Does it > make sense to add them to the tests? Yes, please. See http://gcc.gnu.org/onlinedocs/gccint/Directives.html for documentation on the directives you can use. The testsuite/ directory is also full of examples you can cut-n-paste from. Diego.
Sign in to reply to this message.
On 2011/11/30 13:17:04, Diego Novillo wrote: > On 11-11-30 03:53 , mailto:dvyukov@google.com wrote: > > On 2011/11/14 16:48:45, davidxl wrote: > >> Ok for google/main after compiler bootstrap and regression test > >> (without ftsan), and some large tests with tsan turned on (as many as > >> you can but at your discretion). > > > > Hi David, > > > > Perhaps a bit late question... but better late than never. > > I've added some unit tests, and I only tested them on Linux/x86 (and it > > is the only platform that we currently care about). AFAIR I've seen some > > sort of annotations to restrict tests to particular platforms. Does it > > make sense to add them to the tests? > > Yes, please. See http://gcc.gnu.org/onlinedocs/gccint/Directives.html > for documentation on the directives you can use. The testsuite/ > directory is also full of examples you can cut-n-paste from. Here it is http://codereview.appspot.com/5437087/
Sign in to reply to this message.
ok for google branches. David On Wed, Nov 30, 2011 at 6:09 AM, <dvyukov@google.com> wrote: > On 2011/11/30 13:17:04, Diego Novillo wrote: >> >> On 11-11-30 03:53 , mailto:dvyukov@google.com wrote: >> > On 2011/11/14 16:48:45, davidxl wrote: >> >> Ok for google/main after compiler bootstrap and regression test >> >> (without ftsan), and some large tests with tsan turned on (as many > > as >> >> >> you can but at your discretion). >> > >> > Hi David, >> > >> > Perhaps a bit late question... but better late than never. >> > I've added some unit tests, and I only tested them on Linux/x86 (and > > it >> >> > is the only platform that we currently care about). AFAIR I've seen > > some >> >> > sort of annotations to restrict tests to particular platforms. Does > > it >> >> > make sense to add them to the tests? > >> Yes, please. See http://gcc.gnu.org/onlinedocs/gccint/Directives.html > >> for documentation on the directives you can use. The testsuite/ >> directory is also full of examples you can cut-n-paste from. > > Here it is > http://codereview.appspot.com/5437087/ > > > http://codereview.appspot.com/5303083/ >
Sign in to reply to this message.
|