blob: 33f2a1bfcf5104f5012e9249b7237dc0f915404f [file] [log] [blame]
/* Pointer Bounds Checker optimization pass.
Copyright (C) 2014-2017 Free Software Foundation, Inc.
Contributed by Ilya Enkovich (ilya.enkovich@intel.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 "backend.h"
#include "target.h"
#include "rtl.h"
#include "tree.h"
#include "gimple.h"
#include "tree-pass.h"
#include "ssa.h"
#include "gimple-pretty-print.h"
#include "diagnostic.h"
#include "fold-const.h"
#include "tree-cfg.h"
#include "tree-ssa-loop-niter.h"
#include "gimple-iterator.h"
#include "tree-chkp.h"
#include "ipa-chkp.h"
enum check_type
{
CHECK_LOWER_BOUND,
CHECK_UPPER_BOUND
};
struct pol_item
{
tree cst;
tree var;
};
struct address_t
{
vec<struct pol_item> pol;
};
/* Structure to hold check informtation. */
struct check_info
{
/* Type of the check. */
check_type type;
/* Address used for the check. */
address_t addr;
/* Bounds used for the check. */
tree bounds;
/* Check statement. Can be NULL for removed checks. */
gimple *stmt;
};
/* Structure to hold checks information for BB. */
struct bb_checks
{
vec<struct check_info, va_heap, vl_ptr> checks;
};
static void chkp_collect_value (tree ssa_name, address_t &res);
#define chkp_bndmk_fndecl \
(targetm.builtin_chkp_function (BUILT_IN_CHKP_BNDMK))
#define chkp_intersect_fndecl \
(targetm.builtin_chkp_function (BUILT_IN_CHKP_INTERSECT))
#define chkp_checkl_fndecl \
(targetm.builtin_chkp_function (BUILT_IN_CHKP_BNDCL))
#define chkp_checku_fndecl \
(targetm.builtin_chkp_function (BUILT_IN_CHKP_BNDCU))
static vec<struct bb_checks, va_heap, vl_ptr> check_infos;
/* Comparator for pol_item structures I1 and I2 to be used
to find items with equal var. Also used for polynomial
sorting. */
static int
chkp_pol_item_compare (const void *i1, const void *i2)
{
const struct pol_item *p1 = (const struct pol_item *)i1;
const struct pol_item *p2 = (const struct pol_item *)i2;
if (p1->var == p2->var)
return 0;
else if (p1->var > p2->var)
return 1;
else
return -1;
}
/* Find polynomial item in ADDR with var equal to VAR
and return its index. Return -1 if item was not
found. */
static int
chkp_pol_find (address_t &addr, tree var)
{
int left = 0;
int right = addr.pol.length () - 1;
int n;
while (right >= left)
{
n = (left + right) / 2;
if (addr.pol[n].var == var
|| (var && addr.pol[n].var
&& TREE_CODE (var) == ADDR_EXPR
&& TREE_CODE (addr.pol[n].var) == ADDR_EXPR
&& TREE_OPERAND (var, 0) == TREE_OPERAND (addr.pol[n].var, 0)))
return n;
else if (addr.pol[n].var > var)
right = n - 1;
else
left = n + 1;
}
return -1;
}
/* Return constant CST extended to size type. */
static tree
chkp_extend_const (tree cst)
{
if (TYPE_PRECISION (TREE_TYPE (cst)) < TYPE_PRECISION (size_type_node))
return build_int_cst_type (size_type_node, tree_to_shwi (cst));
return cst;
}
/* Add polynomial item CST * VAR to ADDR. */
static void
chkp_add_addr_item (address_t &addr, tree cst, tree var)
{
int n = chkp_pol_find (addr, var);
cst = chkp_extend_const (cst);
if (n < 0)
{
struct pol_item item;
item.cst = cst;
item.var = var;
addr.pol.safe_push (item);
addr.pol.qsort (&chkp_pol_item_compare);
}
else
{
addr.pol[n].cst = fold_build2 (PLUS_EXPR, TREE_TYPE (addr.pol[n].cst),
addr.pol[n].cst, cst);
if (TREE_CODE (addr.pol[n].cst) == INTEGER_CST
&& integer_zerop (addr.pol[n].cst))
addr.pol.ordered_remove (n);
}
}
/* Subtract polynomial item CST * VAR from ADDR. */
static void
chkp_sub_addr_item (address_t &addr, tree cst, tree var)
{
int n = chkp_pol_find (addr, var);
cst = chkp_extend_const (cst);
if (n < 0)
{
struct pol_item item;
item.cst = fold_build2 (MINUS_EXPR, TREE_TYPE (cst),
integer_zero_node, cst);
item.var = var;
addr.pol.safe_push (item);
addr.pol.qsort (&chkp_pol_item_compare);
}
else
{
addr.pol[n].cst = fold_build2 (MINUS_EXPR, TREE_TYPE (addr.pol[n].cst),
addr.pol[n].cst, cst);
if (TREE_CODE (addr.pol[n].cst) == INTEGER_CST
&& integer_zerop (addr.pol[n].cst))
addr.pol.ordered_remove (n);
}
}
/* Add address DELTA to ADDR. */
static void
chkp_add_addr_addr (address_t &addr, address_t &delta)
{
unsigned int i;
for (i = 0; i < delta.pol.length (); i++)
chkp_add_addr_item (addr, delta.pol[i].cst, delta.pol[i].var);
}
/* Subtract address DELTA from ADDR. */
static void
chkp_sub_addr_addr (address_t &addr, address_t &delta)
{
unsigned int i;
for (i = 0; i < delta.pol.length (); i++)
chkp_sub_addr_item (addr, delta.pol[i].cst, delta.pol[i].var);
}
/* Mutiply address ADDR by integer constant MULT. */
static void
chkp_mult_addr (address_t &addr, tree mult)
{
unsigned int i;
for (i = 0; i < addr.pol.length (); i++)
addr.pol[i].cst = fold_build2 (MULT_EXPR, TREE_TYPE (addr.pol[i].cst),
addr.pol[i].cst, mult);
}
/* Return 1 if we may prove ADDR has a constant value with
determined sign, which is put into *SIGN. Otherwise
return 0. */
static bool
chkp_is_constant_addr (const address_t &addr, int *sign)
{
*sign = 0;
if (addr.pol.length () == 0)
return true;
else if (addr.pol.length () > 1)
return false;
else if (addr.pol[0].var)
return false;
else if (TREE_CODE (addr.pol[0].cst) != INTEGER_CST)
return false;
else if (integer_zerop (addr.pol[0].cst))
*sign = 0;
else if (tree_int_cst_sign_bit (addr.pol[0].cst))
*sign = -1;
else
*sign = 1;
return true;
}
/* Dump ADDR into dump_file. */
static void
chkp_print_addr (const address_t &addr)
{
unsigned int n = 0;
for (n = 0; n < addr.pol.length (); n++)
{
if (n > 0)
fprintf (dump_file, " + ");
if (addr.pol[n].var == NULL_TREE)
print_generic_expr (dump_file, addr.pol[n].cst);
else
{
if (TREE_CODE (addr.pol[n].cst) != INTEGER_CST
|| !integer_onep (addr.pol[n].cst))
{
print_generic_expr (dump_file, addr.pol[n].cst);
fprintf (dump_file, " * ");
}
print_generic_expr (dump_file, addr.pol[n].var);
}
}
}
/* Compute value of PTR and put it into address RES.
PTR has to be ADDR_EXPR. */
static void
chkp_collect_addr_value (tree ptr, address_t &res)
{
tree obj = TREE_OPERAND (ptr, 0);
address_t addr;
switch (TREE_CODE (obj))
{
case INDIRECT_REF:
chkp_collect_value (TREE_OPERAND (obj, 0), res);
break;
case MEM_REF:
chkp_collect_value (TREE_OPERAND (obj, 0), res);
addr.pol.create (0);
chkp_collect_value (TREE_OPERAND (obj, 1), addr);
chkp_add_addr_addr (res, addr);
addr.pol.release ();
break;
case ARRAY_REF:
chkp_collect_value (build_fold_addr_expr (TREE_OPERAND (obj, 0)), res);
addr.pol.create (0);
chkp_collect_value (TREE_OPERAND (obj, 1), addr);
chkp_mult_addr (addr, array_ref_element_size (obj));
chkp_add_addr_addr (res, addr);
addr.pol.release ();
break;
case COMPONENT_REF:
{
tree str = TREE_OPERAND (obj, 0);
tree field = TREE_OPERAND (obj, 1);
chkp_collect_value (build_fold_addr_expr (str), res);
addr.pol.create (0);
chkp_collect_value (component_ref_field_offset (obj), addr);
chkp_add_addr_addr (res, addr);
addr.pol.release ();
if (DECL_FIELD_BIT_OFFSET (field))
{
addr.pol.create (0);
chkp_collect_value (fold_build2 (TRUNC_DIV_EXPR, size_type_node,
DECL_FIELD_BIT_OFFSET (field),
size_int (BITS_PER_UNIT)),
addr);
chkp_add_addr_addr (res, addr);
addr.pol.release ();
}
}
break;
default:
chkp_add_addr_item (res, integer_one_node, ptr);
break;
}
}
/* Compute value of PTR and put it into address RES. */
static void
chkp_collect_value (tree ptr, address_t &res)
{
gimple *def_stmt;
enum gimple_code code;
enum tree_code rhs_code;
address_t addr;
tree rhs1;
if (TREE_CODE (ptr) == INTEGER_CST)
{
chkp_add_addr_item (res, ptr, NULL);
return;
}
else if (TREE_CODE (ptr) == ADDR_EXPR)
{
chkp_collect_addr_value (ptr, res);
return;
}
else if (TREE_CODE (ptr) != SSA_NAME)
{
chkp_add_addr_item (res, integer_one_node, ptr);
return;
}
/* Now we handle the case when polynomial is computed
for SSA NAME. */
def_stmt = SSA_NAME_DEF_STMT (ptr);
code = gimple_code (def_stmt);
/* Currently we do not walk through statements other
than assignment. */
if (code != GIMPLE_ASSIGN)
{
chkp_add_addr_item (res, integer_one_node, ptr);
return;
}
rhs_code = gimple_assign_rhs_code (def_stmt);
rhs1 = gimple_assign_rhs1 (def_stmt);
switch (rhs_code)
{
case SSA_NAME:
case INTEGER_CST:
case ADDR_EXPR:
chkp_collect_value (rhs1, res);
break;
case PLUS_EXPR:
case POINTER_PLUS_EXPR:
chkp_collect_value (rhs1, res);
addr.pol.create (0);
chkp_collect_value (gimple_assign_rhs2 (def_stmt), addr);
chkp_add_addr_addr (res, addr);
addr.pol.release ();
break;
case MINUS_EXPR:
chkp_collect_value (rhs1, res);
addr.pol.create (0);
chkp_collect_value (gimple_assign_rhs2 (def_stmt), addr);
chkp_sub_addr_addr (res, addr);
addr.pol.release ();
break;
case MULT_EXPR:
if (TREE_CODE (rhs1) == SSA_NAME
&& TREE_CODE (gimple_assign_rhs2 (def_stmt)) == INTEGER_CST)
{
chkp_collect_value (rhs1, res);
chkp_mult_addr (res, gimple_assign_rhs2 (def_stmt));
}
else if (TREE_CODE (gimple_assign_rhs2 (def_stmt)) == SSA_NAME
&& TREE_CODE (rhs1) == INTEGER_CST)
{
chkp_collect_value (gimple_assign_rhs2 (def_stmt), res);
chkp_mult_addr (res, rhs1);
}
else
chkp_add_addr_item (res, integer_one_node, ptr);
break;
default:
chkp_add_addr_item (res, integer_one_node, ptr);
break;
}
}
/* Fill check_info structure *CI with information about
check STMT. */
static void
chkp_fill_check_info (gimple *stmt, struct check_info *ci)
{
ci->addr.pol.create (0);
ci->bounds = gimple_call_arg (stmt, 1);
chkp_collect_value (gimple_call_arg (stmt, 0), ci->addr);
ci->type = (gimple_call_fndecl (stmt) == chkp_checkl_fndecl
? CHECK_LOWER_BOUND
: CHECK_UPPER_BOUND);
ci->stmt = stmt;
}
/* Release structures holding check information
for current function. */
static void
chkp_release_check_info (void)
{
unsigned int n, m;
if (check_infos.exists ())
{
for (n = 0; n < check_infos.length (); n++)
{
for (m = 0; m < check_infos[n].checks.length (); m++)
if (check_infos[n].checks[m].addr.pol.exists ())
check_infos[n].checks[m].addr.pol.release ();
check_infos[n].checks.release ();
}
check_infos.release ();
}
}
/* Create structures to hold check information
for current function. */
static void
chkp_init_check_info (void)
{
struct bb_checks empty_bbc;
int n;
empty_bbc.checks = vNULL;
chkp_release_check_info ();
check_infos.create (last_basic_block_for_fn (cfun));
for (n = 0; n < last_basic_block_for_fn (cfun); n++)
{
check_infos.safe_push (empty_bbc);
check_infos.last ().checks.create (0);
}
}
/* Find all checks in current function and store info about them
in check_infos. */
static void
chkp_gather_checks_info (void)
{
basic_block bb;
gimple_stmt_iterator i;
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, "Gathering information about checks...\n");
chkp_init_check_info ();
FOR_EACH_BB_FN (bb, cfun)
{
struct bb_checks *bbc = &check_infos[bb->index];
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, "Searching checks in BB%d...\n", bb->index);
for (i = gsi_start_bb (bb); !gsi_end_p (i); gsi_next (&i))
{
gimple *stmt = gsi_stmt (i);
if (gimple_code (stmt) != GIMPLE_CALL)
continue;
if (gimple_call_fndecl (stmt) == chkp_checkl_fndecl
|| gimple_call_fndecl (stmt) == chkp_checku_fndecl)
{
struct check_info ci;
chkp_fill_check_info (stmt, &ci);
bbc->checks.safe_push (ci);
if (dump_file && (dump_flags & TDF_DETAILS))
{
fprintf (dump_file, "Adding check information:\n");
fprintf (dump_file, " bounds: ");
print_generic_expr (dump_file, ci.bounds);
fprintf (dump_file, "\n address: ");
chkp_print_addr (ci.addr);
fprintf (dump_file, "\n check: ");
print_gimple_stmt (dump_file, stmt, 0);
}
}
}
}
}
/* Return 1 if check CI against BOUNDS always pass,
-1 if check CI against BOUNDS always fails and
0 if we cannot compute check result. */
static int
chkp_get_check_result (struct check_info *ci, tree bounds)
{
gimple *bnd_def;
address_t bound_val;
int sign, res = 0;
if (dump_file && (dump_flags & TDF_DETAILS))
{
fprintf (dump_file, "Trying to compute result of the check\n");
fprintf (dump_file, " check: ");
print_gimple_stmt (dump_file, ci->stmt, 0);
fprintf (dump_file, " address: ");
chkp_print_addr (ci->addr);
fprintf (dump_file, "\n bounds: ");
print_generic_expr (dump_file, bounds);
fprintf (dump_file, "\n");
}
if (TREE_CODE (bounds) != SSA_NAME)
{
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, " result: bounds tree code is not ssa_name\n");
return 0;
}
bnd_def = SSA_NAME_DEF_STMT (bounds);
/* Currently we handle cases when bounds are result of bndmk
or loaded static bounds var. */
if (gimple_code (bnd_def) == GIMPLE_CALL
&& gimple_call_fndecl (bnd_def) == chkp_bndmk_fndecl)
{
bound_val.pol.create (0);
chkp_collect_value (gimple_call_arg (bnd_def, 0), bound_val);
if (ci->type == CHECK_UPPER_BOUND)
{
address_t size_val;
size_val.pol.create (0);
chkp_collect_value (gimple_call_arg (bnd_def, 1), size_val);
chkp_add_addr_addr (bound_val, size_val);
size_val.pol.release ();
chkp_add_addr_item (bound_val, integer_minus_one_node, NULL);
}
}
else if (gimple_code (bnd_def) == GIMPLE_ASSIGN
&& gimple_assign_rhs1 (bnd_def) == chkp_get_zero_bounds_var ())
{
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, " result: always pass with zero bounds\n");
return 1;
}
else if (gimple_code (bnd_def) == GIMPLE_ASSIGN
&& gimple_assign_rhs1 (bnd_def) == chkp_get_none_bounds_var ())
{
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, " result: always fails with none bounds\n");
return -1;
}
else if (gimple_code (bnd_def) == GIMPLE_ASSIGN
&& TREE_CODE (gimple_assign_rhs1 (bnd_def)) == VAR_DECL)
{
tree bnd_var = gimple_assign_rhs1 (bnd_def);
tree var;
tree size;
if (!DECL_INITIAL (bnd_var)
|| DECL_INITIAL (bnd_var) == error_mark_node)
{
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, " result: cannot compute bounds\n");
return 0;
}
gcc_assert (TREE_CODE (DECL_INITIAL (bnd_var)) == ADDR_EXPR);
var = TREE_OPERAND (DECL_INITIAL (bnd_var), 0);
bound_val.pol.create (0);
chkp_collect_value (DECL_INITIAL (bnd_var), bound_val);
if (ci->type == CHECK_UPPER_BOUND)
{
if (VAR_P (var))
{
if (DECL_SIZE (var)
&& !chkp_variable_size_type (TREE_TYPE (var)))
size = DECL_SIZE_UNIT (var);
else
{
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, " result: cannot compute bounds\n");
return 0;
}
}
else
{
gcc_assert (TREE_CODE (var) == STRING_CST);
size = build_int_cst (size_type_node,
TREE_STRING_LENGTH (var));
}
address_t size_val;
size_val.pol.create (0);
chkp_collect_value (size, size_val);
chkp_add_addr_addr (bound_val, size_val);
size_val.pol.release ();
chkp_add_addr_item (bound_val, integer_minus_one_node, NULL);
}
}
else
{
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, " result: cannot compute bounds\n");
return 0;
}
if (dump_file && (dump_flags & TDF_DETAILS))
{
fprintf (dump_file, " bound value: ");
chkp_print_addr (bound_val);
fprintf (dump_file, "\n");
}
chkp_sub_addr_addr (bound_val, ci->addr);
if (!chkp_is_constant_addr (bound_val, &sign))
{
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, " result: cannot compute result\n");
res = 0;
}
else if (sign == 0
|| (ci->type == CHECK_UPPER_BOUND && sign > 0)
|| (ci->type == CHECK_LOWER_BOUND && sign < 0))
{
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, " result: always pass\n");
res = 1;
}
else
{
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, " result: always fail\n");
res = -1;
}
bound_val.pol.release ();
return res;
}
/* Try to compare bounds value and address value
used in the check CI. If we can prove that check
always pass then remove it. */
static void
chkp_remove_check_if_pass (struct check_info *ci)
{
int result = 0;
if (dump_file && (dump_flags & TDF_DETAILS))
{
fprintf (dump_file, "Trying to remove check: ");
print_gimple_stmt (dump_file, ci->stmt, 0);
}
result = chkp_get_check_result (ci, ci->bounds);
if (result == 1)
{
gimple_stmt_iterator i = gsi_for_stmt (ci->stmt);
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, " action: delete check (always pass)\n");
gsi_remove (&i, true);
unlink_stmt_vdef (ci->stmt);
release_defs (ci->stmt);
ci->stmt = NULL;
}
else if (result == -1)
{
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, " action: keep check (always fail)\n");
warning_at (gimple_location (ci->stmt), OPT_Wchkp,
"memory access check always fail");
}
else if (result == 0)
{
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, " action: keep check (cannot compute result)\n");
}
}
/* For bounds used in CI check if bounds are produced by
intersection and we may use outer bounds instead. If
transformation is possible then fix check statement and
recompute its info. */
static void
chkp_use_outer_bounds_if_possible (struct check_info *ci)
{
gimple *bnd_def;
tree bnd1, bnd2, bnd_res = NULL;
int check_res1, check_res2;
if (TREE_CODE (ci->bounds) != SSA_NAME)
return;
bnd_def = SSA_NAME_DEF_STMT (ci->bounds);
if (gimple_code (bnd_def) != GIMPLE_CALL
|| gimple_call_fndecl (bnd_def) != chkp_intersect_fndecl)
return;
if (dump_file && (dump_flags & TDF_DETAILS))
{
fprintf (dump_file, "Check if bounds intersection is redundant: \n");
fprintf (dump_file, " check: ");
print_gimple_stmt (dump_file, ci->stmt, 0);
fprintf (dump_file, " intersection: ");
print_gimple_stmt (dump_file, bnd_def, 0);
fprintf (dump_file, "\n");
}
bnd1 = gimple_call_arg (bnd_def, 0);
bnd2 = gimple_call_arg (bnd_def, 1);
check_res1 = chkp_get_check_result (ci, bnd1);
check_res2 = chkp_get_check_result (ci, bnd2);
if (check_res1 == 1)
bnd_res = bnd2;
else if (check_res1 == -1)
bnd_res = bnd1;
else if (check_res2 == 1)
bnd_res = bnd1;
else if (check_res2 == -1)
bnd_res = bnd2;
if (bnd_res)
{
if (dump_file && (dump_flags & TDF_DETAILS))
{
fprintf (dump_file, " action: use ");
print_generic_expr (dump_file, bnd2);
fprintf (dump_file, " instead of ");
print_generic_expr (dump_file, ci->bounds);
fprintf (dump_file, "\n");
}
ci->bounds = bnd_res;
gimple_call_set_arg (ci->stmt, 1, bnd_res);
update_stmt (ci->stmt);
chkp_fill_check_info (ci->stmt, ci);
}
}
/* Try to find checks whose bounds were produced by intersection
which does not affect check result. In such check outer bounds
are used instead. It allows to remove excess intersections
and helps to compare checks. */
static void
chkp_remove_excess_intersections (void)
{
basic_block bb;
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, "Searching for redundant bounds intersections...\n");
FOR_EACH_BB_FN (bb, cfun)
{
struct bb_checks *bbc = &check_infos[bb->index];
unsigned int no;
/* Iterate through all found checks in BB. */
for (no = 0; no < bbc->checks.length (); no++)
if (bbc->checks[no].stmt)
chkp_use_outer_bounds_if_possible (&bbc->checks[no]);
}
}
/* Try to remove all checks which are known to alwyas pass. */
static void
chkp_remove_constant_checks (void)
{
basic_block bb;
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, "Searching for redundant checks...\n");
FOR_EACH_BB_FN (bb, cfun)
{
struct bb_checks *bbc = &check_infos[bb->index];
unsigned int no;
/* Iterate through all found checks in BB. */
for (no = 0; no < bbc->checks.length (); no++)
if (bbc->checks[no].stmt)
chkp_remove_check_if_pass (&bbc->checks[no]);
}
}
/* Return fast version of string function FNCODE. */
static tree
chkp_get_nobnd_fndecl (enum built_in_function fncode)
{
/* Check if we are allowed to use fast string functions. */
if (!flag_chkp_use_fast_string_functions)
return NULL_TREE;
tree fndecl = NULL_TREE;
switch (fncode)
{
case BUILT_IN_MEMCPY_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMCPY_NOBND);
break;
case BUILT_IN_MEMPCPY_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMPCPY_NOBND);
break;
case BUILT_IN_MEMMOVE_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMMOVE_NOBND);
break;
case BUILT_IN_MEMSET_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMSET_NOBND);
break;
case BUILT_IN_CHKP_MEMCPY_NOCHK_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMCPY_NOBND_NOCHK);
break;
case BUILT_IN_CHKP_MEMPCPY_NOCHK_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMPCPY_NOBND_NOCHK);
break;
case BUILT_IN_CHKP_MEMMOVE_NOCHK_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMMOVE_NOBND_NOCHK);
break;
case BUILT_IN_CHKP_MEMSET_NOCHK_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMSET_NOBND_NOCHK);
break;
default:
break;
}
if (fndecl)
fndecl = chkp_maybe_clone_builtin_fndecl (fndecl);
return fndecl;
}
/* Return no-check version of string function FNCODE. */
static tree
chkp_get_nochk_fndecl (enum built_in_function fncode)
{
/* Check if we are allowed to use fast string functions. */
if (!flag_chkp_use_nochk_string_functions)
return NULL_TREE;
tree fndecl = NULL_TREE;
switch (fncode)
{
case BUILT_IN_MEMCPY_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMCPY_NOCHK);
break;
case BUILT_IN_MEMPCPY_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMPCPY_NOCHK);
break;
case BUILT_IN_MEMMOVE_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMMOVE_NOCHK);
break;
case BUILT_IN_MEMSET_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMSET_NOCHK);
break;
case BUILT_IN_CHKP_MEMCPY_NOBND_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMCPY_NOBND_NOCHK);
break;
case BUILT_IN_CHKP_MEMPCPY_NOBND_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMPCPY_NOBND_NOCHK);
break;
case BUILT_IN_CHKP_MEMMOVE_NOBND_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMMOVE_NOBND_NOCHK);
break;
case BUILT_IN_CHKP_MEMSET_NOBND_CHKP:
fndecl = builtin_decl_implicit (BUILT_IN_CHKP_MEMSET_NOBND_NOCHK);
break;
default:
break;
}
if (fndecl)
fndecl = chkp_maybe_clone_builtin_fndecl (fndecl);
return fndecl;
}
/* Find memcpy, mempcpy, memmove and memset calls, perform
checks before call and then call no_chk version of
functions. We do it on O2 to enable inlining of these
functions during expand.
Also try to find memcpy, mempcpy, memmove and memset calls
which are known to not write pointers to memory and use
faster function versions for them. */
static void
chkp_optimize_string_function_calls (void)
{
basic_block bb;
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, "Searching for replaceable string function calls...\n");
FOR_EACH_BB_FN (bb, cfun)
{
gimple_stmt_iterator i;
for (i = gsi_start_bb (bb); !gsi_end_p (i); gsi_next (&i))
{
gimple *stmt = gsi_stmt (i);
tree fndecl;
if (!is_gimple_call (stmt)
|| !gimple_call_with_bounds_p (stmt)
|| !gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
continue;
fndecl = gimple_call_fndecl (stmt);
if (DECL_FUNCTION_CODE (fndecl) == BUILT_IN_MEMCPY_CHKP
|| DECL_FUNCTION_CODE (fndecl) == BUILT_IN_MEMPCPY_CHKP
|| DECL_FUNCTION_CODE (fndecl) == BUILT_IN_MEMMOVE_CHKP
|| DECL_FUNCTION_CODE (fndecl) == BUILT_IN_MEMSET_CHKP)
{
tree dst = gimple_call_arg (stmt, 0);
tree dst_bnd = gimple_call_arg (stmt, 1);
bool is_memset = DECL_FUNCTION_CODE (fndecl) == BUILT_IN_MEMSET_CHKP;
tree size = gimple_call_arg (stmt, is_memset ? 3 : 4);
tree fndecl_nochk;
gimple_stmt_iterator j;
basic_block check_bb;
address_t size_val;
int sign;
bool known;
/* We may replace call with corresponding __chkp_*_nobnd
call in case destination pointer base type is not
void or pointer. */
if (POINTER_TYPE_P (TREE_TYPE (dst))
&& !VOID_TYPE_P (TREE_TYPE (TREE_TYPE (dst)))
&& !chkp_type_has_pointer (TREE_TYPE (TREE_TYPE (dst))))
{
tree fndecl_nobnd
= chkp_get_nobnd_fndecl (DECL_FUNCTION_CODE (fndecl));
if (fndecl_nobnd)
fndecl = fndecl_nobnd;
}
fndecl_nochk = chkp_get_nochk_fndecl (DECL_FUNCTION_CODE (fndecl));
if (fndecl_nochk)
fndecl = fndecl_nochk;
if (fndecl != gimple_call_fndecl (stmt))
{
if (dump_file && (dump_flags & TDF_DETAILS))
{
fprintf (dump_file, "Replacing call: ");
print_gimple_stmt (dump_file, stmt, 0,
TDF_VOPS|TDF_MEMSYMS);
}
gimple_call_set_fndecl (stmt, fndecl);
if (dump_file && (dump_flags & TDF_DETAILS))
{
fprintf (dump_file, "With a new call: ");
print_gimple_stmt (dump_file, stmt, 0,
TDF_VOPS|TDF_MEMSYMS);
}
}
/* If there is no nochk version of function then
do nothing. Otherwise insert checks before
the call. */
if (!fndecl_nochk)
continue;
/* If size passed to call is known and > 0
then we may insert checks unconditionally. */
size_val.pol.create (0);
chkp_collect_value (size, size_val);
known = chkp_is_constant_addr (size_val, &sign);
size_val.pol.release ();
/* If we are not sure size is not zero then we have
to perform runtime check for size and perform
checks only when size is not zero. */
if (!known)
{
gimple *check = gimple_build_cond (NE_EXPR,
size,
size_zero_node,
NULL_TREE,
NULL_TREE);
/* Split block before string function call. */
gsi_prev (&i);
check_bb = insert_cond_bb (bb, gsi_stmt (i), check,
profile_probability::likely ());
/* Set position for checks. */
j = gsi_last_bb (check_bb);
/* The block was splitted and therefore we
need to set iterator to its end. */
i = gsi_last_bb (bb);
}
/* If size is known to be zero then no checks
should be performed. */
else if (!sign)
continue;
else
j = i;
size = size_binop (MINUS_EXPR, size, size_one_node);
if (!is_memset)
{
tree src = gimple_call_arg (stmt, 2);
tree src_bnd = gimple_call_arg (stmt, 3);
chkp_check_mem_access (src, fold_build_pointer_plus (src, size),
src_bnd, j, gimple_location (stmt),
integer_zero_node);
}
chkp_check_mem_access (dst, fold_build_pointer_plus (dst, size),
dst_bnd, j, gimple_location (stmt),
integer_one_node);
}
}
}
}
/* Intrumentation pass inserts most of bounds creation code
in the header of the function. We want to move bounds
creation closer to bounds usage to reduce bounds lifetime.
We also try to avoid bounds creation code on paths where
bounds are not used. */
static void
chkp_reduce_bounds_lifetime (void)
{
basic_block bb = FALLTHRU_EDGE (ENTRY_BLOCK_PTR_FOR_FN (cfun))->dest;
gimple_stmt_iterator i;
for (i = gsi_start_bb (bb); !gsi_end_p (i); )
{
gimple *dom_use, *use_stmt, *stmt = gsi_stmt (i);
basic_block dom_bb;
ssa_op_iter iter;
imm_use_iterator use_iter;
use_operand_p use_p;
tree op;
bool want_move = false;
bool deps = false;
if (gimple_code (stmt) == GIMPLE_CALL
&& gimple_call_fndecl (stmt) == chkp_bndmk_fndecl)
want_move = true;
if (gimple_code (stmt) == GIMPLE_ASSIGN
&& POINTER_BOUNDS_P (gimple_assign_lhs (stmt))
&& gimple_assign_rhs_code (stmt) == VAR_DECL)
want_move = true;
if (!want_move)
{
gsi_next (&i);
continue;
}
/* Check we do not increase other values lifetime. */
FOR_EACH_PHI_OR_STMT_USE (use_p, stmt, iter, SSA_OP_USE)
{
op = USE_FROM_PTR (use_p);
if (TREE_CODE (op) == SSA_NAME
&& gimple_code (SSA_NAME_DEF_STMT (op)) != GIMPLE_NOP)
{
deps = true;
break;
}
}
if (deps)
{
gsi_next (&i);
continue;
}
/* Check all usages of bounds. */
if (gimple_code (stmt) == GIMPLE_CALL)
op = gimple_call_lhs (stmt);
else
{
gcc_assert (gimple_code (stmt) == GIMPLE_ASSIGN);
op = gimple_assign_lhs (stmt);
}
dom_use = NULL;
dom_bb = NULL;
FOR_EACH_IMM_USE_STMT (use_stmt, use_iter, op)
{
if (is_gimple_debug (use_stmt))
continue;
if (dom_bb &&
dominated_by_p (CDI_DOMINATORS,
dom_bb, gimple_bb (use_stmt)))
{
dom_use = use_stmt;
dom_bb = NULL;
}
else if (dom_bb)
dom_bb = nearest_common_dominator (CDI_DOMINATORS, dom_bb,
gimple_bb (use_stmt));
else if (!dom_use)
dom_use = use_stmt;
else if (stmt_dominates_stmt_p (use_stmt, dom_use))
dom_use = use_stmt;
else if (!stmt_dominates_stmt_p (dom_use, use_stmt)
/* If dom_use and use_stmt are PHI nodes in one BB
then it is OK to keep any of them as dom_use.
stmt_dominates_stmt_p returns 0 for such
combination, so check it here manually. */
&& (gimple_code (dom_use) != GIMPLE_PHI
|| gimple_code (use_stmt) != GIMPLE_PHI
|| gimple_bb (use_stmt) != gimple_bb (dom_use))
)
{
dom_bb = nearest_common_dominator (CDI_DOMINATORS,
gimple_bb (use_stmt),
gimple_bb (dom_use));
dom_use = NULL;
}
}
/* In case there is a single use, just move bounds
creation to the use. */
if (dom_use || dom_bb)
{
if (dump_file && (dump_flags & TDF_DETAILS))
{
fprintf (dump_file, "Moving creation of ");
print_generic_expr (dump_file, op);
fprintf (dump_file, " down to its use.\n");
}
if (dom_use && gimple_code (dom_use) == GIMPLE_PHI)
{
dom_bb = get_immediate_dominator (CDI_DOMINATORS,
gimple_bb (dom_use));
dom_use = NULL;
}
if (dom_bb == bb
|| (dom_use && gimple_bb (dom_use) == bb))
{
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file, "Cannot move statement bacause there is no "
"suitable dominator block other than entry block.\n");
gsi_next (&i);
}
else
{
if (dom_bb)
{
gimple_stmt_iterator last = gsi_last_bb (dom_bb);
if (!gsi_end_p (last) && stmt_ends_bb_p (gsi_stmt (last)))
gsi_move_before (&i, &last);
else
gsi_move_after (&i, &last);
}
else
{
gimple_stmt_iterator gsi = gsi_for_stmt (dom_use);
gsi_move_before (&i, &gsi);
}
gimple_set_vdef (stmt, NULL_TREE);
gimple_set_vuse (stmt, NULL_TREE);
update_stmt (stmt);
}
}
else
gsi_next (&i);
}
}
/* Initilize checker optimization pass. */
static void
chkp_opt_init (void)
{
check_infos.create (0);
calculate_dominance_info (CDI_DOMINATORS);
calculate_dominance_info (CDI_POST_DOMINATORS);
/* With LTO constant bounds vars may be not initialized by now.
Get constant bounds vars to handle their assignments during
optimizations. */
chkp_get_zero_bounds_var ();
chkp_get_none_bounds_var ();
}
/* Finalise checker optimization pass. */
static void
chkp_opt_fini (void)
{
chkp_fix_cfg ();
free_dominance_info (CDI_POST_DOMINATORS);
}
/* Checker optimization pass function. */
static unsigned int
chkp_opt_execute (void)
{
chkp_opt_init();
/* This optimization may introduce new checks
and thus we put it before checks search. */
chkp_optimize_string_function_calls ();
chkp_gather_checks_info ();
chkp_remove_excess_intersections ();
chkp_remove_constant_checks ();
chkp_reduce_bounds_lifetime ();
chkp_release_check_info ();
chkp_opt_fini ();
return 0;
}
/* Pass gate. */
static bool
chkp_opt_gate (void)
{
return chkp_function_instrumented_p (cfun->decl)
&& (flag_chkp_optimize > 0
|| (flag_chkp_optimize == -1 && optimize > 0));
}
namespace {
const pass_data pass_data_chkp_opt =
{
GIMPLE_PASS, /* type */
"chkpopt", /* name */
OPTGROUP_NONE, /* optinfo_flags */
TV_NONE, /* tv_id */
PROP_ssa | PROP_cfg, /* properties_required */
0, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
TODO_verify_il
| TODO_update_ssa /* todo_flags_finish */
};
class pass_chkp_opt : public gimple_opt_pass
{
public:
pass_chkp_opt (gcc::context *ctxt)
: gimple_opt_pass (pass_data_chkp_opt, ctxt)
{}
/* opt_pass methods: */
virtual opt_pass * clone ()
{
return new pass_chkp_opt (m_ctxt);
}
virtual bool gate (function *)
{
return chkp_opt_gate ();
}
virtual unsigned int execute (function *)
{
return chkp_opt_execute ();
}
}; // class pass_chkp_opt
} // anon namespace
gimple_opt_pass *
make_pass_chkp_opt (gcc::context *ctxt)
{
return new pass_chkp_opt (ctxt);
}