sanopt.c: Include tree-ssa-operands.h.

* sanopt.c: Include tree-ssa-operands.h.
	(struct sanopt_info): Add has_freeing_call_p,
	has_freeing_call_computed_p, imm_dom_path_with_freeing_call_p,
	imm_dom_path_with_freeing_call_computed_p, freeing_call_events,
	being_visited_p fields.
	(struct sanopt_ctx): Add asan_check_map field.
	(imm_dom_path_with_freeing_call, maybe_optimize_ubsan_null_ifn,
	maybe_optimize_asan_check_ifn): New functions.
	(sanopt_optimize_walker): Use them, optimize even ASAN_CHECK
	internal calls.
	(pass_sanopt::execute): Call sanopt_optimize even for
	-fsanitize=address.
	* gimple.c (nonfreeing_call_p): Return true for non-ECF_LEAF
	internal calls.

Co-Authored-By: Marek Polacek <polacek@redhat.com>

From-SVN: r217581
This commit is contained in:
Jakub Jelinek 2014-11-14 18:28:11 +01:00 committed by Jakub Jelinek
parent 18d11814af
commit ab9a433037
3 changed files with 393 additions and 91 deletions

View File

@ -1,3 +1,21 @@
2014-11-14 Jakub Jelinek <jakub@redhat.com>
Marek Polacek <polacek@redhat.com>
* sanopt.c: Include tree-ssa-operands.h.
(struct sanopt_info): Add has_freeing_call_p,
has_freeing_call_computed_p, imm_dom_path_with_freeing_call_p,
imm_dom_path_with_freeing_call_computed_p, freeing_call_events,
being_visited_p fields.
(struct sanopt_ctx): Add asan_check_map field.
(imm_dom_path_with_freeing_call, maybe_optimize_ubsan_null_ifn,
maybe_optimize_asan_check_ifn): New functions.
(sanopt_optimize_walker): Use them, optimize even ASAN_CHECK
internal calls.
(pass_sanopt::execute): Call sanopt_optimize even for
-fsanitize=address.
* gimple.c (nonfreeing_call_p): Return true for non-ECF_LEAF
internal calls.
2014-11-14 Alan Lawrence <alan.lawrence@arm.com> 2014-11-14 Alan Lawrence <alan.lawrence@arm.com>
* tree-vect-loop.c (vect_create_epilog_for_reduction): Move code for * tree-vect-loop.c (vect_create_epilog_for_reduction): Move code for

View File

@ -2538,6 +2538,9 @@ nonfreeing_call_p (gimple call)
default: default:
return true; return true;
} }
else if (gimple_call_internal_p (call)
&& gimple_call_flags (call) & ECF_LEAF)
return true;
return false; return false;
} }

View File

@ -49,6 +49,7 @@ along with GCC; see the file COPYING3. If not see
#include "langhooks.h" #include "langhooks.h"
#include "ubsan.h" #include "ubsan.h"
#include "params.h" #include "params.h"
#include "tree-ssa-operands.h"
/* This is used to carry information about basic blocks. It is /* This is used to carry information about basic blocks. It is
@ -56,7 +57,30 @@ along with GCC; see the file COPYING3. If not see
struct sanopt_info struct sanopt_info
{ {
/* True if this BB has been visited. */ /* True if this BB might call (directly or indirectly) free/munmap
or similar operation. */
bool has_freeing_call_p;
/* True if HAS_FREEING_CALL_P flag has been computed. */
bool has_freeing_call_computed_p;
/* True if there is a block with HAS_FREEING_CALL_P flag set
on any path between an immediate dominator of BB, denoted
imm(BB), and BB. */
bool imm_dom_path_with_freeing_call_p;
/* True if IMM_DOM_PATH_WITH_FREEING_CALL_P has been computed. */
bool imm_dom_path_with_freeing_call_computed_p;
/* Number of possibly freeing calls encountered in this bb
(so far). */
uint64_t freeing_call_events;
/* True if BB is currently being visited during computation
of IMM_DOM_PATH_WITH_FREEING_CALL_P flag. */
bool being_visited_p;
/* True if this BB has been visited in the dominator walk. */
bool visited_p; bool visited_p;
}; };
@ -69,131 +93,387 @@ struct sanopt_ctx
a vector of UBSAN_NULL call statements that check this pointer. */ a vector of UBSAN_NULL call statements that check this pointer. */
hash_map<tree, auto_vec<gimple> > null_check_map; hash_map<tree, auto_vec<gimple> > null_check_map;
/* This map maps a pointer (the second argument of ASAN_CHECK) to
a vector of ASAN_CHECK call statements that check the access. */
hash_map<tree, auto_vec<gimple> > asan_check_map;
/* Number of IFN_ASAN_CHECK statements. */ /* Number of IFN_ASAN_CHECK statements. */
int asan_num_accesses; int asan_num_accesses;
}; };
/* Try to optimize away redundant UBSAN_NULL checks. /* Return true if there might be any call to free/munmap operation
on any path in between DOM (which should be imm(BB)) and BB. */
static bool
imm_dom_path_with_freeing_call (basic_block bb, basic_block dom)
{
sanopt_info *info = (sanopt_info *) bb->aux;
edge e;
edge_iterator ei;
if (info->imm_dom_path_with_freeing_call_computed_p)
return info->imm_dom_path_with_freeing_call_p;
info->being_visited_p = true;
FOR_EACH_EDGE (e, ei, bb->preds)
{
sanopt_info *pred_info = (sanopt_info *) e->src->aux;
if (e->src == dom)
continue;
if ((pred_info->imm_dom_path_with_freeing_call_computed_p
&& pred_info->imm_dom_path_with_freeing_call_p)
|| (pred_info->has_freeing_call_computed_p
&& pred_info->has_freeing_call_p))
{
info->imm_dom_path_with_freeing_call_computed_p = true;
info->imm_dom_path_with_freeing_call_p = true;
info->being_visited_p = false;
return true;
}
}
FOR_EACH_EDGE (e, ei, bb->preds)
{
sanopt_info *pred_info = (sanopt_info *) e->src->aux;
if (e->src == dom)
continue;
if (pred_info->has_freeing_call_computed_p)
continue;
gimple_stmt_iterator gsi;
for (gsi = gsi_start_bb (e->src); !gsi_end_p (gsi); gsi_next (&gsi))
{
gimple stmt = gsi_stmt (gsi);
if (is_gimple_call (stmt) && !nonfreeing_call_p (stmt))
{
pred_info->has_freeing_call_p = true;
break;
}
}
pred_info->has_freeing_call_computed_p = true;
if (pred_info->has_freeing_call_p)
{
info->imm_dom_path_with_freeing_call_computed_p = true;
info->imm_dom_path_with_freeing_call_p = true;
info->being_visited_p = false;
return true;
}
}
FOR_EACH_EDGE (e, ei, bb->preds)
{
if (e->src == dom)
continue;
basic_block src;
for (src = e->src; src != dom; )
{
sanopt_info *pred_info = (sanopt_info *) src->aux;
if (pred_info->being_visited_p)
break;
basic_block imm = get_immediate_dominator (CDI_DOMINATORS, src);
if (imm_dom_path_with_freeing_call (src, imm))
{
info->imm_dom_path_with_freeing_call_computed_p = true;
info->imm_dom_path_with_freeing_call_p = true;
info->being_visited_p = false;
return true;
}
src = imm;
}
}
info->imm_dom_path_with_freeing_call_computed_p = true;
info->imm_dom_path_with_freeing_call_p = false;
info->being_visited_p = false;
return false;
}
/* Optimize away redundant UBSAN_NULL calls. */
static bool
maybe_optimize_ubsan_null_ifn (struct sanopt_ctx *ctx, gimple stmt)
{
gcc_assert (gimple_call_num_args (stmt) == 3);
tree ptr = gimple_call_arg (stmt, 0);
tree cur_align = gimple_call_arg (stmt, 2);
gcc_assert (TREE_CODE (cur_align) == INTEGER_CST);
bool remove = false;
auto_vec<gimple> &v = ctx->null_check_map.get_or_insert (ptr);
if (v.is_empty ())
{
/* For this PTR we don't have any UBSAN_NULL stmts recorded, so there's
nothing to optimize yet. */
v.safe_push (stmt);
return false;
}
/* We already have recorded a UBSAN_NULL check for this pointer. Perhaps we
can drop this one. But only if this check doesn't specify stricter
alignment. */
while (!v.is_empty ())
{
gimple g = v.last ();
/* Remove statements for BBs that have been already processed. */
sanopt_info *si = (sanopt_info *) gimple_bb (g)->aux;
if (si->visited_p)
{
v.pop ();
continue;
}
/* At this point we shouldn't have any statements that aren't dominating
the current BB. */
tree align = gimple_call_arg (g, 2);
int kind = tree_to_shwi (gimple_call_arg (g, 1));
/* If this is a NULL pointer check where we had segv anyway, we can
remove it. */
if (integer_zerop (align)
&& (kind == UBSAN_LOAD_OF
|| kind == UBSAN_STORE_OF
|| kind == UBSAN_MEMBER_ACCESS))
remove = true;
/* Otherwise remove the check in non-recovering mode, or if the
stmts have same location. */
else if (integer_zerop (align))
remove = (flag_sanitize_recover & SANITIZE_NULL) == 0
|| flag_sanitize_undefined_trap_on_error
|| gimple_location (g) == gimple_location (stmt);
else if (tree_int_cst_le (cur_align, align))
remove = (flag_sanitize_recover & SANITIZE_ALIGNMENT) == 0
|| flag_sanitize_undefined_trap_on_error
|| gimple_location (g) == gimple_location (stmt);
if (!remove && gimple_bb (g) == gimple_bb (stmt)
&& tree_int_cst_compare (cur_align, align) == 0)
v.pop ();
break;
}
if (!remove)
v.safe_push (stmt);
return remove;
}
/* Optimize away redundant ASAN_CHECK calls. */
static bool
maybe_optimize_asan_check_ifn (struct sanopt_ctx *ctx, gimple stmt)
{
gcc_assert (gimple_call_num_args (stmt) == 4);
tree ptr = gimple_call_arg (stmt, 1);
tree len = gimple_call_arg (stmt, 2);
basic_block bb = gimple_bb (stmt);
sanopt_info *info = (sanopt_info *) bb->aux;
if (TREE_CODE (len) != INTEGER_CST)
return false;
if (integer_zerop (len))
return false;
gimple_set_uid (stmt, info->freeing_call_events);
auto_vec<gimple> &v = ctx->asan_check_map.get_or_insert (ptr);
if (v.is_empty ())
{
/* For this PTR we don't have any ASAN_CHECK stmts recorded, so there's
nothing to optimize yet. */
v.safe_push (stmt);
return false;
}
/* We already have recorded a ASAN_CHECK check for this pointer. Perhaps
we can drop this one. But only if this check doesn't specify larger
size. */
while (!v.is_empty ())
{
gimple g = v.last ();
/* Remove statements for BBs that have been already processed. */
sanopt_info *si = (sanopt_info *) gimple_bb (g)->aux;
if (si->visited_p)
v.pop ();
else
break;
}
unsigned int i;
gimple g;
gimple to_pop = NULL;
bool remove = false;
basic_block last_bb = bb;
bool cleanup = false;
FOR_EACH_VEC_ELT_REVERSE (v, i, g)
{
basic_block gbb = gimple_bb (g);
sanopt_info *si = (sanopt_info *) gbb->aux;
if (gimple_uid (g) < si->freeing_call_events)
{
/* If there is a potentially freeing call after g in gbb, we should
remove it from the vector, can't use in optimization. */
cleanup = true;
continue;
}
if (TREE_CODE (len) != INTEGER_CST)
{
/* If there is some stmts not followed by freeing call event
for ptr already in the current bb, no need to insert anything.
Non-constant len is treated just as length 1. */
if (gbb == bb)
return false;
break;
}
tree glen = gimple_call_arg (g, 2);
/* If we've checked only smaller length than we want to check now,
we can't remove the current stmt. If g is in the same basic block,
we want to remove it though, as the current stmt is better. */
if (tree_int_cst_lt (glen, len))
{
if (gbb == bb)
{
to_pop = g;
cleanup = true;
}
continue;
}
while (last_bb != gbb)
{
/* Paths from last_bb to bb have been checked before.
gbb is necessarily a dominator of last_bb, but not necessarily
immediate dominator. */
if (((sanopt_info *) last_bb->aux)->freeing_call_events)
break;
basic_block imm = get_immediate_dominator (CDI_DOMINATORS, last_bb);
gcc_assert (imm);
if (imm_dom_path_with_freeing_call (last_bb, imm))
break;
last_bb = imm;
}
if (last_bb == gbb)
remove = true;
break;
}
if (cleanup)
{
unsigned int j = 0, l = v.length ();
for (i = 0; i < l; i++)
if (v[i] != to_pop
&& (gimple_uid (v[i])
== ((sanopt_info *)
gimple_bb (v[i])->aux)->freeing_call_events))
{
if (i != j)
v[j] = v[i];
j++;
}
v.truncate (j);
}
if (!remove)
v.safe_push (stmt);
return remove;
}
/* Try to optimize away redundant UBSAN_NULL and ASAN_CHECK calls.
We walk blocks in the CFG via a depth first search of the dominator We walk blocks in the CFG via a depth first search of the dominator
tree; we push unique UBSAN_NULL statements into a vector in the tree; we push unique UBSAN_NULL or ASAN_CHECK statements into a vector
NULL_CHECK_MAP as we enter the blocks. When leaving a block, we in the NULL_CHECK_MAP or ASAN_CHECK_MAP hash maps as we enter the
mark the block as visited; then when checking the statements in the blocks. When leaving a block, we mark the block as visited; then
vector, we ignore statements that are coming from already visited when checking the statements in the vector, we ignore statements that
blocks, because these cannot dominate anything anymore. are coming from already visited blocks, because these cannot dominate
CTX is a sanopt context. */ anything anymore. CTX is a sanopt context. */
static void static void
sanopt_optimize_walker (basic_block bb, struct sanopt_ctx *ctx) sanopt_optimize_walker (basic_block bb, struct sanopt_ctx *ctx)
{ {
basic_block son; basic_block son;
gimple_stmt_iterator gsi; gimple_stmt_iterator gsi;
sanopt_info *info = (sanopt_info *) bb->aux;
bool asan_check_optimize
= (flag_sanitize & SANITIZE_ADDRESS)
&& ((flag_sanitize & flag_sanitize_recover
& SANITIZE_KERNEL_ADDRESS) == 0);
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi);) for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi);)
{ {
gimple stmt = gsi_stmt (gsi); gimple stmt = gsi_stmt (gsi);
bool remove = false; bool remove = false;
if (is_gimple_call (stmt) if (!is_gimple_call (stmt))
&& gimple_call_internal_p (stmt)) {
/* Handle asm volatile or asm with "memory" clobber
the same as potentionally freeing call. */
if (gimple_code (stmt) == GIMPLE_ASM
&& asan_check_optimize
&& (gimple_asm_clobbers_memory_p (stmt)
|| gimple_asm_volatile_p (stmt)))
info->freeing_call_events++;
gsi_next (&gsi);
continue;
}
if (asan_check_optimize && !nonfreeing_call_p (stmt))
info->freeing_call_events++;
if (gimple_call_internal_p (stmt))
switch (gimple_call_internal_fn (stmt)) switch (gimple_call_internal_fn (stmt))
{ {
case IFN_UBSAN_NULL: case IFN_UBSAN_NULL:
{ remove = maybe_optimize_ubsan_null_ifn (ctx, stmt);
gcc_assert (gimple_call_num_args (stmt) == 3); break;
tree ptr = gimple_call_arg (stmt, 0);
tree cur_align = gimple_call_arg (stmt, 2);
gcc_assert (TREE_CODE (cur_align) == INTEGER_CST);
auto_vec<gimple> &v = ctx->null_check_map.get_or_insert (ptr);
if (v.is_empty ())
/* For this PTR we don't have any UBSAN_NULL stmts
recorded, so there's nothing to optimize yet. */
v.safe_push (stmt);
else
{
/* We already have recorded a UBSAN_NULL check
for this pointer. Perhaps we can drop this one.
But only if this check doesn't specify stricter
alignment. */
while (!v.is_empty ())
{
gimple g = v.last ();
/* Remove statements for BBs that have been
already processed. */
sanopt_info *si = (sanopt_info *) gimple_bb (g)->aux;
if (si->visited_p)
v.pop ();
else
{
/* At this point we shouldn't have any statements
that aren't dominating the current BB. */
tree align = gimple_call_arg (g, 2);
int kind = tree_to_shwi (gimple_call_arg (g, 1));
/* If this is a NULL pointer check where we had segv
anyway, we can remove it. */
if (integer_zerop (align)
&& (kind == UBSAN_LOAD_OF
|| kind == UBSAN_STORE_OF
|| kind == UBSAN_MEMBER_ACCESS))
remove = true;
/* Otherwise remove the check in non-recovering
mode, or if the stmts have same location. */
else if (integer_zerop (align))
remove = !(flag_sanitize_recover & SANITIZE_NULL)
|| flag_sanitize_undefined_trap_on_error
|| gimple_location (g)
== gimple_location (stmt);
else if (tree_int_cst_le (cur_align, align))
remove = !(flag_sanitize_recover
& SANITIZE_ALIGNMENT)
|| flag_sanitize_undefined_trap_on_error
|| gimple_location (g)
== gimple_location (stmt);
if (!remove && gimple_bb (g) == gimple_bb (stmt)
&& tree_int_cst_compare (cur_align, align) == 0)
v.pop ();
break;
}
}
if (remove)
{
/* Drop this check. */
if (dump_file && (dump_flags & TDF_DETAILS))
{
fprintf (dump_file, "Optimizing out\n ");
print_gimple_stmt (dump_file, stmt, 0,
dump_flags);
fprintf (dump_file, "\n");
}
gsi_remove (&gsi, true);
}
else
v.safe_push (stmt);
}
}
case IFN_ASAN_CHECK: case IFN_ASAN_CHECK:
ctx->asan_num_accesses++; if (asan_check_optimize)
remove = maybe_optimize_asan_check_ifn (ctx, stmt);
if (!remove)
ctx->asan_num_accesses++;
break; break;
default: default:
break; break;
} }
/* If we were able to remove the current statement, gsi_remove if (remove)
already pointed us to the next statement. */ {
if (!remove) /* Drop this check. */
if (dump_file && (dump_flags & TDF_DETAILS))
{
fprintf (dump_file, "Optimizing out\n ");
print_gimple_stmt (dump_file, stmt, 0, dump_flags);
fprintf (dump_file, "\n");
}
unlink_stmt_vdef (stmt);
gsi_remove (&gsi, true);
}
else
gsi_next (&gsi); gsi_next (&gsi);
} }
if (asan_check_optimize)
{
info->has_freeing_call_p = info->freeing_call_events != 0;
info->has_freeing_call_computed_p = true;
}
for (son = first_dom_son (CDI_DOMINATORS, bb); for (son = first_dom_son (CDI_DOMINATORS, bb);
son; son;
son = next_dom_son (CDI_DOMINATORS, son)) son = next_dom_son (CDI_DOMINATORS, son))
sanopt_optimize_walker (son, ctx); sanopt_optimize_walker (son, ctx);
/* We're leaving this BB, so mark it to that effect. */ /* We're leaving this BB, so mark it to that effect. */
sanopt_info *info = (sanopt_info *) bb->aux;
info->visited_p = true; info->visited_p = true;
} }
@ -259,7 +539,8 @@ pass_sanopt::execute (function *fun)
/* Try to remove redundant checks. */ /* Try to remove redundant checks. */
if (optimize if (optimize
&& (flag_sanitize & (SANITIZE_NULL | SANITIZE_ALIGNMENT))) && (flag_sanitize
& (SANITIZE_NULL | SANITIZE_ALIGNMENT | SANITIZE_ADDRESS)))
asan_num_accesses = sanopt_optimize (fun); asan_num_accesses = sanopt_optimize (fun);
else if (flag_sanitize & SANITIZE_ADDRESS) else if (flag_sanitize & SANITIZE_ADDRESS)
{ {