Introduce -finstrument-functions-once
The goal is to make it possible to use it in (large) production binaries to do function-level coverage, so the overhead must be minimum and, in particular, there is no protection against data races so the "once" moniker is imprecise. gcc/ * common.opt (finstrument-functions): Set explicit value. (-finstrument-functions-once): New option. * doc/invoke.texi (Program Instrumentation Options): Document it. * gimplify.cc (build_instrumentation_call): New static function. (gimplify_function_tree): Call it to emit the instrumentation calls if -finstrument-functions[-once] is specified. gcc/testsuite/ * gcc.dg/instrument-4.c: New test.
This commit is contained in:
parent
cb1ecf3819
commit
3b598848f6
4 changed files with 130 additions and 32 deletions
|
@ -1890,9 +1890,13 @@ EnumValue
|
|||
Enum(cf_protection_level) String(none) Value(CF_NONE)
|
||||
|
||||
finstrument-functions
|
||||
Common Var(flag_instrument_function_entry_exit)
|
||||
Common Var(flag_instrument_function_entry_exit,1)
|
||||
Instrument function entry and exit with profiling calls.
|
||||
|
||||
finstrument-functions-once
|
||||
Common Var(flag_instrument_function_entry_exit,2)
|
||||
Instrument function entry and exit with profiling calls invoked once.
|
||||
|
||||
finstrument-functions-exclude-function-list=
|
||||
Common RejectNegative Joined
|
||||
-finstrument-functions-exclude-function-list=name,... Do not instrument listed functions.
|
||||
|
|
|
@ -618,7 +618,7 @@ Objective-C and Objective-C++ Dialects}.
|
|||
-fno-stack-limit -fsplit-stack @gol
|
||||
-fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
|
||||
-fvtv-counts -fvtv-debug @gol
|
||||
-finstrument-functions @gol
|
||||
-finstrument-functions -finstrument-functions-once @gol
|
||||
-finstrument-functions-exclude-function-list=@var{sym},@var{sym},@dots{} @gol
|
||||
-finstrument-functions-exclude-file-list=@var{file},@var{file},@dots{}} @gol
|
||||
-fprofile-prefix-map=@var{old}=@var{new}
|
||||
|
@ -16395,6 +16395,22 @@ cannot safely be called (perhaps signal handlers, if the profiling
|
|||
routines generate output or allocate memory).
|
||||
@xref{Common Function Attributes}.
|
||||
|
||||
@item -finstrument-functions-once
|
||||
@opindex -finstrument-functions-once
|
||||
This is similar to @option{-finstrument-functions}, but the profiling
|
||||
functions are called only once per instrumented function, i.e. the first
|
||||
profiling function is called after the first entry into the instrumented
|
||||
function and the second profiling function is called before the exit
|
||||
corresponding to this first entry.
|
||||
|
||||
The definition of @code{once} for the purpose of this option is a little
|
||||
vague because the implementation is not protected against data races.
|
||||
As a result, the implementation only guarantees that the profiling
|
||||
functions are called at @emph{least} once per process and at @emph{most}
|
||||
once per thread, but the calls are always paired, that is to say, if a
|
||||
thread calls the first function, then it will call the second function,
|
||||
unless it never reaches the exit of the instrumented function.
|
||||
|
||||
@item -finstrument-functions-exclude-file-list=@var{file},@var{file},@dots{}
|
||||
@opindex finstrument-functions-exclude-file-list
|
||||
|
||||
|
|
131
gcc/gimplify.cc
131
gcc/gimplify.cc
|
@ -16586,6 +16586,51 @@ flag_instrument_functions_exclude_p (tree fndecl)
|
|||
return false;
|
||||
}
|
||||
|
||||
/* Build a call to the instrumentation function FNCODE and add it to SEQ.
|
||||
If COND_VAR is not NULL, it is a boolean variable guarding the call to
|
||||
the instrumentation function. IF STMT is not NULL, it is a statement
|
||||
to be executed just before the call to the instrumentation function. */
|
||||
|
||||
static void
|
||||
build_instrumentation_call (gimple_seq *seq, enum built_in_function fncode,
|
||||
tree cond_var, gimple *stmt)
|
||||
{
|
||||
/* The instrumentation hooks aren't going to call the instrumented
|
||||
function and the address they receive is expected to be matchable
|
||||
against symbol addresses. Make sure we don't create a trampoline,
|
||||
in case the current function is nested. */
|
||||
tree this_fn_addr = build_fold_addr_expr (current_function_decl);
|
||||
TREE_NO_TRAMPOLINE (this_fn_addr) = 1;
|
||||
|
||||
tree label_true, label_false;
|
||||
if (cond_var)
|
||||
{
|
||||
label_true = create_artificial_label (UNKNOWN_LOCATION);
|
||||
label_false = create_artificial_label (UNKNOWN_LOCATION);
|
||||
gcond *cond = gimple_build_cond (EQ_EXPR, cond_var, boolean_false_node,
|
||||
label_true, label_false);
|
||||
gimplify_seq_add_stmt (seq, cond);
|
||||
gimplify_seq_add_stmt (seq, gimple_build_label (label_true));
|
||||
gimplify_seq_add_stmt (seq, gimple_build_predict (PRED_COLD_LABEL,
|
||||
NOT_TAKEN));
|
||||
}
|
||||
|
||||
if (stmt)
|
||||
gimplify_seq_add_stmt (seq, stmt);
|
||||
|
||||
tree x = builtin_decl_implicit (BUILT_IN_RETURN_ADDRESS);
|
||||
gcall *call = gimple_build_call (x, 1, integer_zero_node);
|
||||
tree tmp_var = create_tmp_var (ptr_type_node, "return_addr");
|
||||
gimple_call_set_lhs (call, tmp_var);
|
||||
gimplify_seq_add_stmt (seq, call);
|
||||
x = builtin_decl_implicit (fncode);
|
||||
call = gimple_build_call (x, 2, this_fn_addr, tmp_var);
|
||||
gimplify_seq_add_stmt (seq, call);
|
||||
|
||||
if (cond_var)
|
||||
gimplify_seq_add_stmt (seq, gimple_build_label (label_false));
|
||||
}
|
||||
|
||||
/* Entry point to the gimplification pass. FNDECL is the FUNCTION_DECL
|
||||
node for the function we want to gimplify.
|
||||
|
||||
|
@ -16636,40 +16681,66 @@ gimplify_function_tree (tree fndecl)
|
|||
&& DECL_DISREGARD_INLINE_LIMITS (fndecl))
|
||||
&& !flag_instrument_functions_exclude_p (fndecl))
|
||||
{
|
||||
tree x;
|
||||
gbind *new_bind;
|
||||
gimple *tf;
|
||||
gimple_seq cleanup = NULL, body = NULL;
|
||||
tree tmp_var, this_fn_addr;
|
||||
gcall *call;
|
||||
gimple_seq body = NULL, cleanup = NULL;
|
||||
gassign *assign;
|
||||
tree cond_var;
|
||||
|
||||
/* The instrumentation hooks aren't going to call the instrumented
|
||||
function and the address they receive is expected to be matchable
|
||||
against symbol addresses. Make sure we don't create a trampoline,
|
||||
in case the current function is nested. */
|
||||
this_fn_addr = build_fold_addr_expr (current_function_decl);
|
||||
TREE_NO_TRAMPOLINE (this_fn_addr) = 1;
|
||||
/* If -finstrument-functions-once is specified, generate:
|
||||
|
||||
x = builtin_decl_implicit (BUILT_IN_RETURN_ADDRESS);
|
||||
call = gimple_build_call (x, 1, integer_zero_node);
|
||||
tmp_var = create_tmp_var (ptr_type_node, "return_addr");
|
||||
gimple_call_set_lhs (call, tmp_var);
|
||||
gimplify_seq_add_stmt (&cleanup, call);
|
||||
x = builtin_decl_implicit (BUILT_IN_PROFILE_FUNC_EXIT);
|
||||
call = gimple_build_call (x, 2, this_fn_addr, tmp_var);
|
||||
gimplify_seq_add_stmt (&cleanup, call);
|
||||
tf = gimple_build_try (seq, cleanup, GIMPLE_TRY_FINALLY);
|
||||
static volatile bool C.0 = false;
|
||||
bool tmp_called;
|
||||
|
||||
x = builtin_decl_implicit (BUILT_IN_RETURN_ADDRESS);
|
||||
call = gimple_build_call (x, 1, integer_zero_node);
|
||||
tmp_var = create_tmp_var (ptr_type_node, "return_addr");
|
||||
gimple_call_set_lhs (call, tmp_var);
|
||||
gimplify_seq_add_stmt (&body, call);
|
||||
x = builtin_decl_implicit (BUILT_IN_PROFILE_FUNC_ENTER);
|
||||
call = gimple_build_call (x, 2, this_fn_addr, tmp_var);
|
||||
gimplify_seq_add_stmt (&body, call);
|
||||
tmp_called = C.0;
|
||||
if (!tmp_called)
|
||||
{
|
||||
C.0 = true;
|
||||
[call profiling enter function]
|
||||
}
|
||||
|
||||
without specific protection for data races. */
|
||||
if (flag_instrument_function_entry_exit > 1)
|
||||
{
|
||||
tree first_var
|
||||
= build_decl (DECL_SOURCE_LOCATION (current_function_decl),
|
||||
VAR_DECL,
|
||||
create_tmp_var_name ("C"),
|
||||
boolean_type_node);
|
||||
DECL_ARTIFICIAL (first_var) = 1;
|
||||
DECL_IGNORED_P (first_var) = 1;
|
||||
TREE_STATIC (first_var) = 1;
|
||||
TREE_THIS_VOLATILE (first_var) = 1;
|
||||
TREE_USED (first_var) = 1;
|
||||
DECL_INITIAL (first_var) = boolean_false_node;
|
||||
varpool_node::add (first_var);
|
||||
|
||||
cond_var = create_tmp_var (boolean_type_node, "tmp_called");
|
||||
assign = gimple_build_assign (cond_var, first_var);
|
||||
gimplify_seq_add_stmt (&body, assign);
|
||||
|
||||
assign = gimple_build_assign (first_var, boolean_true_node);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
cond_var = NULL_TREE;
|
||||
assign = NULL;
|
||||
}
|
||||
|
||||
build_instrumentation_call (&body, BUILT_IN_PROFILE_FUNC_ENTER,
|
||||
cond_var, assign);
|
||||
|
||||
/* If -finstrument-functions-once is specified, generate:
|
||||
|
||||
if (!tmp_called)
|
||||
[call profiling exit function]
|
||||
|
||||
without specific protection for data races. */
|
||||
build_instrumentation_call (&cleanup, BUILT_IN_PROFILE_FUNC_EXIT,
|
||||
cond_var, NULL);
|
||||
|
||||
gimple *tf = gimple_build_try (seq, cleanup, GIMPLE_TRY_FINALLY);
|
||||
gimplify_seq_add_stmt (&body, tf);
|
||||
new_bind = gimple_build_bind (NULL, body, NULL);
|
||||
gbind *new_bind = gimple_build_bind (NULL, body, NULL);
|
||||
|
||||
/* Replace the current function body with the body
|
||||
wrapped in the try/finally TF. */
|
||||
|
|
7
gcc/testsuite/gcc.dg/instrument-4.c
Normal file
7
gcc/testsuite/gcc.dg/instrument-4.c
Normal file
|
@ -0,0 +1,7 @@
|
|||
/* { dg-do compile } */
|
||||
/* { dg-options "-finstrument-functions-once" } */
|
||||
|
||||
void fn () { }
|
||||
|
||||
/* { dg-final { scan-assembler "__cyg_profile_func_enter" } } */
|
||||
/* { dg-final { scan-assembler "__cyg_profile_func_exit" } } */
|
Loading…
Add table
Reference in a new issue