1866 lines
44 KiB
C
1866 lines
44 KiB
C
/*
|
|
* Copyright 2015-2017 by Emese Revfy <re.emese@gmail.com>
|
|
* Licensed under the GPL v2
|
|
*
|
|
* Homepage:
|
|
* https://github.com/ephox-gcc-plugins/initify
|
|
*
|
|
* This plugin has two passes. The first one tries to find all functions that
|
|
* can be become __init/__exit. The second one moves string constants
|
|
* (local variables and function string arguments marked by
|
|
* the nocapture attribute) only referenced in __init/__exit functions
|
|
* to __initconst/__exitconst sections.
|
|
* Based on an idea from Mathias Krause <minipli@ld-linux.so>.
|
|
*
|
|
* The instrumentation pass of the latent_entropy plugin must run after
|
|
* the initify plugin to increase coverage.
|
|
*
|
|
* Options:
|
|
* -fplugin-arg-initify_plugin-disable
|
|
* -fplugin-arg-initify_plugin-verbose
|
|
* -fplugin-arg-initify_plugin-print_missing_attr
|
|
* -fplugin-arg-initify_plugin-search_init_exit_functions
|
|
* -fplugin-arg-initify_plugin-enable_init_to_exit_moves
|
|
* -fplugin-arg-initify_plugin-disable_verify_nocapture_functions
|
|
*
|
|
* Attribute: __attribute__((nocapture(x, y ...)))
|
|
* The nocapture gcc attribute can be on functions only.
|
|
* The attribute takes one or more unsigned integer constants as parameters
|
|
* that specify the function argument(s) of const char* type to initify.
|
|
* If the marked argument is a vararg then the plugin initifies
|
|
* all vararg arguments.
|
|
* There can be one negative value which means that the return of the function
|
|
* will be followed to find it is a nocapture attribute or not.
|
|
*
|
|
* Attribute: __attribute__((unverified_nocapture(x, y ...)))
|
|
* This attribute disables the compile data flow verification of the designated
|
|
* nocapture parameters of the function. Use it only on function parameters
|
|
* that are difficult for the plugin to analyze.
|
|
*
|
|
* Usage:
|
|
* $ make
|
|
* $ make run
|
|
*/
|
|
|
|
#include "gcc-common.h"
|
|
|
|
__visible int plugin_is_GPL_compatible;
|
|
|
|
static struct plugin_info initify_plugin_info = {
|
|
.version = "20170215",
|
|
.help = "disable\tturn off the initify plugin\n"
|
|
"verbose\tprint all initified strings and all"
|
|
" functions which should be __init/__exit\n"
|
|
"print_missing_attr\tprint functions which"
|
|
" can be marked by nocapture attribute\n"
|
|
"search_init_exit_functions\tfind functions"
|
|
" which should be marked by __init or __exit"
|
|
" attribute\n"
|
|
"enable_init_to_exit_moves\tmove a function"
|
|
" to the exit section if it is called by __init"
|
|
" and __exit functions too\n"
|
|
"disable_verify_nocapture_functions\tdisable"
|
|
" the search of capture uses in nocapture"
|
|
" functions\n"
|
|
};
|
|
|
|
#define ARGNUM_NONE 0
|
|
static bool verbose, print_missing_attr, search_init_exit_functions;
|
|
static bool enable_init_to_exit_moves, disable_verify_nocapture_functions;
|
|
|
|
enum section_type {
|
|
INIT, EXIT, BOTH, NONE
|
|
};
|
|
|
|
enum attribute_type {
|
|
UNVERIFIED, NOCAPTURE, PRINTF, BUILTINS, SYSCALL, NONE_ATTRIBUTE
|
|
};
|
|
|
|
|
|
#if BUILDING_GCC_VERSION >= 5000
|
|
typedef struct hash_set<const_gimple> gimple_set;
|
|
|
|
static inline bool pointer_set_insert(gimple_set *visited, const_gimple stmt)
|
|
{
|
|
return visited->add(stmt);
|
|
}
|
|
|
|
static inline bool pointer_set_contains(gimple_set *visited, const_gimple stmt)
|
|
{
|
|
return visited->contains(stmt);
|
|
}
|
|
|
|
static inline gimple_set* pointer_set_create(void)
|
|
{
|
|
return new hash_set<const_gimple>;
|
|
}
|
|
|
|
static inline void pointer_set_destroy(gimple_set *visited)
|
|
{
|
|
delete visited;
|
|
}
|
|
|
|
typedef struct hash_set<const_tree> tree_set;
|
|
|
|
static inline bool pointer_set_insert(tree_set *visited, const_tree node)
|
|
{
|
|
return visited->add(node);
|
|
}
|
|
|
|
static inline tree_set* tree_pointer_set_create(void)
|
|
{
|
|
return new hash_set<const_tree>;
|
|
}
|
|
|
|
static inline void pointer_set_destroy(tree_set *visited)
|
|
{
|
|
delete visited;
|
|
}
|
|
|
|
typedef struct hash_set<struct cgraph_node *> cgraph_set;
|
|
|
|
static inline bool pointer_set_insert(cgraph_set *visited, struct cgraph_node *node)
|
|
{
|
|
return visited->add(node);
|
|
}
|
|
|
|
static inline cgraph_set* cgraph_pointer_set_create(void)
|
|
{
|
|
return new hash_set<struct cgraph_node *>;
|
|
}
|
|
|
|
static inline void pointer_set_destroy(cgraph_set *visited)
|
|
{
|
|
delete visited;
|
|
}
|
|
#else
|
|
typedef struct pointer_set_t gimple_set;
|
|
typedef struct pointer_set_t tree_set;
|
|
typedef struct pointer_set_t cgraph_set;
|
|
|
|
static inline tree_set *tree_pointer_set_create(void)
|
|
{
|
|
return pointer_set_create();
|
|
}
|
|
|
|
static inline cgraph_set *cgraph_pointer_set_create(void)
|
|
{
|
|
return pointer_set_create();
|
|
}
|
|
#endif
|
|
|
|
static gimple initify_get_def_stmt(const_tree node)
|
|
{
|
|
gcc_assert(node != NULL_TREE);
|
|
|
|
if (TREE_CODE(node) != SSA_NAME)
|
|
return NULL;
|
|
return SSA_NAME_DEF_STMT(node);
|
|
}
|
|
|
|
static void search_constant_strings(bool *has_str_cst, gimple_set *visited, tree node);
|
|
static bool has_capture_use_local_var(const_tree vardecl);
|
|
static bool search_capture_ssa_use(gimple_set *visited_defs, tree node);
|
|
|
|
#define FUNCTION_PTR_P(node) \
|
|
(TREE_CODE(TREE_TYPE(node)) == POINTER_TYPE && \
|
|
(TREE_CODE(TREE_TYPE(TREE_TYPE(node))) == FUNCTION_TYPE || \
|
|
TREE_CODE(TREE_TYPE(TREE_TYPE(node))) == METHOD_TYPE))
|
|
|
|
static bool is_vararg_arg(tree arg_list, unsigned int num)
|
|
{
|
|
if (tree_last(arg_list) == void_list_node)
|
|
return false;
|
|
|
|
return num >= (unsigned int)list_length(arg_list);
|
|
}
|
|
|
|
static const_tree get_ptr_type(const_tree type)
|
|
{
|
|
gcc_assert(type != NULL_TREE);
|
|
|
|
if (TREE_CODE(type) != POINTER_TYPE)
|
|
return type;
|
|
return get_ptr_type(TREE_TYPE(type));
|
|
}
|
|
|
|
static bool check_parameter(tree *node, tree type_args, int idx)
|
|
{
|
|
const_tree type_arg, type, type_type, type_name, ptr_type;
|
|
|
|
if (is_vararg_arg(type_args, idx))
|
|
return true;
|
|
|
|
type_arg = chain_index(idx - 1, type_args);
|
|
type = TREE_VALUE(type_arg);
|
|
gcc_assert(type != NULL_TREE);
|
|
type_type = TREE_TYPE(type);
|
|
gcc_assert(type_type != NULL_TREE);
|
|
|
|
type_name = TYPE_NAME(type_type);
|
|
if (type_name != NULL_TREE && TREE_CODE(type_name) == IDENTIFIER_NODE && !strcmp(TYPE_NAME_POINTER(type_type), "va_format"))
|
|
return true;
|
|
|
|
if (TREE_CODE(type) != POINTER_TYPE) {
|
|
error("%u. parameter of the %qE function must be a pointer", idx, *node);
|
|
return false;
|
|
}
|
|
|
|
ptr_type = get_ptr_type(type_type);
|
|
if (!TYPE_READONLY(ptr_type)) {
|
|
error("%u. parameter of the %qE function must be readonly", idx, *node);
|
|
return false;
|
|
}
|
|
|
|
if (TREE_THIS_VOLATILE(ptr_type)) {
|
|
error("%u. parameter of the %qE function can't be volatile", idx, *node);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool check_marked_parameters(tree *node, tree type_args, const_tree args, const_tree name)
|
|
{
|
|
const_tree arg;
|
|
bool negative_val;
|
|
|
|
negative_val = false;
|
|
for (arg = args; arg; arg = TREE_CHAIN(arg)) {
|
|
int idx;
|
|
unsigned int abs_idx;
|
|
tree position = TREE_VALUE(arg);
|
|
|
|
if (TREE_CODE(position) != INTEGER_CST) {
|
|
error("%qE parameter of the %qE attribute isn't an integer (fn: %qE)", position, name, *node);
|
|
return false;
|
|
}
|
|
|
|
idx = (int)tree_to_shwi(position);
|
|
if (negative_val && idx < 0) {
|
|
error("Only one negative attribute value is supported (attribute: %qE fn: %qE)", name, *node);
|
|
return false;
|
|
}
|
|
|
|
if (idx < 0)
|
|
negative_val = true;
|
|
|
|
abs_idx = abs(idx);
|
|
if (abs_idx == 0)
|
|
continue;
|
|
|
|
if (!check_parameter(node, type_args, abs_idx))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool check_all_parameters(tree *node, tree type_args)
|
|
{
|
|
int arg, len = list_length(type_args);
|
|
|
|
if (tree_last(type_args) == void_list_node)
|
|
len -= 1;
|
|
|
|
for (arg = 1; arg <= len; arg++) {
|
|
if (!check_parameter(node, type_args, arg))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* nocapture attribute:
|
|
* * to mark nocapture function arguments. If used on a vararg argument
|
|
* it applies to all of them that have no other uses.
|
|
* * attribute value 0 is ignored to allow reusing print attribute arguments
|
|
*/
|
|
static bool handle_initify_attributes(tree *node, tree name, tree args)
|
|
{
|
|
tree type_args = NULL_TREE;
|
|
|
|
switch (TREE_CODE(*node)) {
|
|
case FUNCTION_DECL:
|
|
type_args = TYPE_ARG_TYPES(TREE_TYPE(*node));
|
|
break;
|
|
|
|
case FUNCTION_TYPE:
|
|
case METHOD_TYPE:
|
|
type_args = TYPE_ARG_TYPES(*node);
|
|
break;
|
|
|
|
case TYPE_DECL: {
|
|
enum tree_code fn_code;
|
|
const_tree fntype = TREE_TYPE(*node);
|
|
|
|
fn_code = TREE_CODE(fntype);
|
|
if (fn_code == POINTER_TYPE)
|
|
fntype = TREE_TYPE(fntype);
|
|
fn_code = TREE_CODE(fntype);
|
|
if (fn_code == FUNCTION_TYPE || fn_code == METHOD_TYPE) {
|
|
type_args = TYPE_ARG_TYPES(fntype);
|
|
break;
|
|
}
|
|
/* FALLTHROUGH */
|
|
}
|
|
|
|
default:
|
|
debug_tree(*node);
|
|
error("%s: %qE attribute only applies to functions", __func__, name);
|
|
return false;
|
|
}
|
|
|
|
gcc_assert(type_args != NULL_TREE);
|
|
|
|
if (!check_marked_parameters(node, type_args, args, name))
|
|
return false;
|
|
return args != NULL_TREE || check_all_parameters(node, type_args);
|
|
}
|
|
|
|
static tree handle_nocapture_attribute(tree *node, tree name, tree args, int __unused flags, bool *no_add_attrs)
|
|
{
|
|
tree nocapture_attr;
|
|
|
|
*no_add_attrs = true;
|
|
|
|
if (!handle_initify_attributes(node, name, args))
|
|
return NULL_TREE;
|
|
|
|
nocapture_attr = lookup_attribute("nocapture", DECL_ATTRIBUTES(*node));
|
|
if (nocapture_attr)
|
|
chainon(TREE_VALUE(nocapture_attr), args);
|
|
else
|
|
*no_add_attrs = false;
|
|
|
|
return NULL_TREE;
|
|
}
|
|
|
|
static tree handle_unverified_nocapture_attribute(tree *node, tree name, tree args, int __unused flags, bool *no_add_attrs)
|
|
{
|
|
tree unverified_attr;
|
|
|
|
*no_add_attrs = true;
|
|
|
|
if (!handle_initify_attributes(node, name, args))
|
|
return NULL_TREE;
|
|
|
|
unverified_attr = lookup_attribute("unverified_nocapture", DECL_ATTRIBUTES(*node));
|
|
if (unverified_attr)
|
|
chainon(TREE_VALUE(unverified_attr), args);
|
|
else
|
|
*no_add_attrs = false;
|
|
|
|
return NULL_TREE;
|
|
}
|
|
|
|
static struct attribute_spec nocapture_attr = {
|
|
.name = "nocapture",
|
|
.min_length = 0,
|
|
.max_length = -1,
|
|
.decl_required = true,
|
|
.type_required = false,
|
|
.function_type_required = false,
|
|
.handler = handle_nocapture_attribute,
|
|
#if BUILDING_GCC_VERSION >= 4007
|
|
.affects_type_identity = false
|
|
#endif
|
|
};
|
|
|
|
static struct attribute_spec unverified_nocapture_attr = {
|
|
.name = "unverified_nocapture",
|
|
.min_length = 0,
|
|
.max_length = -1,
|
|
.decl_required = true,
|
|
.type_required = false,
|
|
.function_type_required = false,
|
|
.handler = handle_unverified_nocapture_attribute,
|
|
#if BUILDING_GCC_VERSION >= 4007
|
|
.affects_type_identity = false
|
|
#endif
|
|
};
|
|
|
|
static void register_attributes(void __unused *event_data, void __unused *data)
|
|
{
|
|
register_attribute(&nocapture_attr);
|
|
register_attribute(&unverified_nocapture_attr);
|
|
}
|
|
|
|
/* Determine whether the function is in the init or exit sections. */
|
|
static enum section_type get_init_exit_section(const_tree decl)
|
|
{
|
|
const char *str;
|
|
const_tree section, attr_value;
|
|
|
|
section = lookup_attribute("section", DECL_ATTRIBUTES(decl));
|
|
if (!section)
|
|
return NONE;
|
|
|
|
attr_value = TREE_VALUE(section);
|
|
gcc_assert(attr_value != NULL_TREE);
|
|
gcc_assert(list_length(attr_value) == 1);
|
|
|
|
str = TREE_STRING_POINTER(TREE_VALUE(attr_value));
|
|
|
|
if (!strncmp(str, ".init.", 6))
|
|
return INIT;
|
|
if (!strncmp(str, ".exit.", 6))
|
|
return EXIT;
|
|
return NONE;
|
|
}
|
|
|
|
static tree get_string_cst(tree var)
|
|
{
|
|
if (var == NULL_TREE)
|
|
return NULL_TREE;
|
|
|
|
if (TREE_CODE(var) == STRING_CST)
|
|
return var;
|
|
|
|
switch (TREE_CODE_CLASS(TREE_CODE(var))) {
|
|
case tcc_expression:
|
|
case tcc_reference: {
|
|
int i;
|
|
|
|
for (i = 0; i < TREE_OPERAND_LENGTH(var); i++) {
|
|
tree ret = get_string_cst(TREE_OPERAND(var, i));
|
|
if (ret != NULL_TREE)
|
|
return ret;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NULL_TREE;
|
|
}
|
|
|
|
static bool set_init_exit_section(tree decl)
|
|
{
|
|
gcc_assert(DECL_P(decl));
|
|
|
|
if (get_init_exit_section(decl) != NONE)
|
|
return false;
|
|
|
|
if (get_init_exit_section(current_function_decl) == INIT)
|
|
set_decl_section_name(decl, ".init.rodata.str");
|
|
else
|
|
set_decl_section_name(decl, ".exit.rodata.str");
|
|
return true;
|
|
}
|
|
|
|
/* Syscalls are always nocapture functions. */
|
|
static bool is_syscall(const_tree fn)
|
|
{
|
|
const char *name = DECL_NAME_POINTER(fn);
|
|
|
|
if (!strncmp(name, "sys_", 4))
|
|
return true;
|
|
|
|
if (!strncmp(name, "sys32_", 6))
|
|
return true;
|
|
|
|
if (!strncmp(name, "compat_sys_", 11))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* These builtins are nocapture functions. */
|
|
static bool allowed_builtins(const_tree fn)
|
|
{
|
|
const char *name = DECL_NAME_POINTER(fn);
|
|
|
|
if (!strcmp(name, "__builtin_va_start"))
|
|
return true;
|
|
if (!strcmp(name, "__builtin_expect"))
|
|
return true;
|
|
if (!strcmp(name, "__builtin_memcpy"))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static enum attribute_type search_argnum_in_attribute_params(const_tree attr, int fn_arg_num, int fntype_arg_len)
|
|
{
|
|
const_tree attr_val;
|
|
|
|
for (attr_val = TREE_VALUE(attr); attr_val; attr_val = TREE_CHAIN(attr_val)) {
|
|
int attr_arg_val;
|
|
|
|
if (TREE_CODE(TREE_VALUE(attr_val)) == IDENTIFIER_NODE)
|
|
continue;
|
|
|
|
attr_arg_val = (int)abs(tree_to_shwi(TREE_VALUE(attr_val)));
|
|
if (attr_arg_val == fn_arg_num)
|
|
return NOCAPTURE;
|
|
if (attr_arg_val > fntype_arg_len && fn_arg_num >= attr_arg_val)
|
|
return NOCAPTURE;
|
|
}
|
|
return NONE_ATTRIBUTE;
|
|
}
|
|
|
|
/* Check that fn_arg_num is a nocapture argument, handle cloned functions too. */
|
|
static enum attribute_type lookup_nocapture_argument(const_tree fndecl, const_tree attr, int fn_arg_num, int fntype_arg_len)
|
|
{
|
|
const_tree orig_decl, clone_arg, orig_arg;
|
|
tree decl_list, orig_decl_list;
|
|
enum attribute_type orig_attribute;
|
|
struct cgraph_node *node = cgraph_get_node(fndecl);
|
|
|
|
orig_attribute = search_argnum_in_attribute_params(attr, fn_arg_num, fntype_arg_len);
|
|
if (orig_attribute == NONE_ATTRIBUTE)
|
|
return orig_attribute;
|
|
|
|
gcc_assert(node);
|
|
if (node->clone_of && node->clone.tree_map)
|
|
gcc_assert(!node->clone.args_to_skip);
|
|
|
|
if (!DECL_ARTIFICIAL(fndecl) && DECL_ABSTRACT_ORIGIN(fndecl) == NULL_TREE)
|
|
return orig_attribute;
|
|
|
|
orig_decl = DECL_ABSTRACT_ORIGIN(fndecl);
|
|
gcc_assert(orig_decl != NULL_TREE);
|
|
|
|
decl_list = DECL_ARGUMENTS(fndecl);
|
|
orig_decl_list = DECL_ARGUMENTS(orig_decl);
|
|
|
|
if (decl_list == NULL_TREE || orig_decl_list == NULL_TREE)
|
|
return NONE_ATTRIBUTE;
|
|
|
|
if (list_length(decl_list) == list_length(orig_decl_list))
|
|
return orig_attribute;
|
|
|
|
clone_arg = chain_index(fn_arg_num - 1, decl_list);
|
|
gcc_assert(clone_arg != NULL_TREE);
|
|
|
|
orig_arg = chain_index(fn_arg_num - 1, orig_decl_list);
|
|
gcc_assert(orig_arg != NULL_TREE);
|
|
|
|
if (!strcmp(DECL_NAME_POINTER(clone_arg), DECL_NAME_POINTER(orig_arg)))
|
|
return orig_attribute;
|
|
return NONE_ATTRIBUTE;
|
|
}
|
|
|
|
/* Check whether the function argument is nocapture. */
|
|
static enum attribute_type is_fndecl_nocapture_arg(const_tree fndecl, int fn_arg_num)
|
|
{
|
|
int fntype_arg_len;
|
|
const_tree type, attr = NULL_TREE;
|
|
bool fnptr = FUNCTION_PTR_P(fndecl);
|
|
|
|
if (!fnptr && is_syscall(fndecl))
|
|
return SYSCALL;
|
|
|
|
if (!fnptr && DECL_BUILT_IN(fndecl) && allowed_builtins(fndecl))
|
|
return BUILTINS;
|
|
|
|
if (fnptr)
|
|
type = TREE_TYPE(TREE_TYPE(fndecl));
|
|
else
|
|
type = TREE_TYPE(fndecl);
|
|
|
|
fntype_arg_len = type_num_arguments(type);
|
|
|
|
if (!fnptr)
|
|
attr = lookup_attribute("unverified_nocapture", DECL_ATTRIBUTES(fndecl));
|
|
if (attr != NULL_TREE && lookup_nocapture_argument(fndecl, attr, fn_arg_num, fntype_arg_len) != NONE_ATTRIBUTE)
|
|
return UNVERIFIED;
|
|
|
|
attr = lookup_attribute("format", TYPE_ATTRIBUTES(type));
|
|
if (attr != NULL_TREE && lookup_nocapture_argument(fndecl, attr, fn_arg_num, fntype_arg_len) != NONE_ATTRIBUTE)
|
|
return PRINTF;
|
|
|
|
if (fnptr)
|
|
return NONE_ATTRIBUTE;
|
|
|
|
attr = lookup_attribute("nocapture", DECL_ATTRIBUTES(fndecl));
|
|
if (attr == NULL_TREE)
|
|
return NONE_ATTRIBUTE;
|
|
|
|
if (TREE_VALUE(attr) == NULL_TREE)
|
|
return NOCAPTURE;
|
|
|
|
return lookup_nocapture_argument(fndecl, attr, fn_arg_num, fntype_arg_len);
|
|
}
|
|
|
|
/* Check whether arg_num is a nocapture argument that can be returned. */
|
|
static bool is_negative_nocapture_arg(const_tree fndecl, int arg_num)
|
|
{
|
|
const_tree attr, attr_val;
|
|
|
|
gcc_assert(arg_num <= 0);
|
|
|
|
if (FUNCTION_PTR_P(fndecl))
|
|
return false;
|
|
|
|
attr = lookup_attribute("nocapture", DECL_ATTRIBUTES(fndecl));
|
|
if (attr == NULL_TREE)
|
|
return false;
|
|
|
|
for (attr_val = TREE_VALUE(attr); attr_val; attr_val = TREE_CHAIN(attr_val)) {
|
|
int attr_arg_val;
|
|
|
|
if (arg_num == 0 && tree_int_cst_lt(TREE_VALUE(attr_val), integer_zero_node))
|
|
return true;
|
|
|
|
attr_arg_val = (int)tree_to_shwi(TREE_VALUE(attr_val));
|
|
if (attr_arg_val == arg_num)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool is_same_vardecl(const_tree op, const_tree vardecl)
|
|
{
|
|
const_tree decl;
|
|
|
|
if (op == vardecl)
|
|
return true;
|
|
if (TREE_CODE(op) == SSA_NAME)
|
|
decl = SSA_NAME_VAR(op);
|
|
else
|
|
decl = op;
|
|
|
|
if (decl == NULL_TREE || !DECL_P(decl))
|
|
return false;
|
|
|
|
if (TREE_CODE(decl) != TREE_CODE(vardecl))
|
|
return false;
|
|
|
|
return DECL_NAME(decl) && !strcmp(DECL_NAME_POINTER(decl), DECL_NAME_POINTER(vardecl));
|
|
}
|
|
|
|
static bool search_same_vardecl(const_tree value, const_tree vardecl)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < TREE_OPERAND_LENGTH(value); i++) {
|
|
const_tree op = TREE_OPERAND(value, i);
|
|
|
|
if (op == NULL_TREE)
|
|
continue;
|
|
if (is_same_vardecl(op, vardecl))
|
|
return true;
|
|
if (search_same_vardecl(op, vardecl))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool check_constructor(const_tree constructor, const_tree vardecl)
|
|
{
|
|
unsigned HOST_WIDE_INT cnt __unused;
|
|
tree value;
|
|
|
|
FOR_EACH_CONSTRUCTOR_VALUE(CONSTRUCTOR_ELTS(constructor), cnt, value) {
|
|
if (TREE_CODE(value) == CONSTRUCTOR)
|
|
return check_constructor(value, vardecl);
|
|
if (is_gimple_constant(value))
|
|
continue;
|
|
|
|
gcc_assert(TREE_OPERAND_LENGTH(value) > 0);
|
|
if (search_same_vardecl(value, vardecl))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool compare_ops(const_tree vardecl, tree op)
|
|
{
|
|
if (TREE_CODE(op) == TREE_LIST)
|
|
op = TREE_VALUE(op);
|
|
if (TREE_CODE(op) == SSA_NAME)
|
|
op = SSA_NAME_VAR(op);
|
|
if (op == NULL_TREE)
|
|
return false;
|
|
|
|
switch (TREE_CODE_CLASS(TREE_CODE(op))) {
|
|
case tcc_declaration:
|
|
return is_same_vardecl(op, vardecl);
|
|
|
|
case tcc_exceptional:
|
|
return check_constructor(op, vardecl);
|
|
|
|
case tcc_constant:
|
|
case tcc_statement:
|
|
case tcc_comparison:
|
|
return false;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
gcc_assert(TREE_OPERAND_LENGTH(op) > 0);
|
|
return search_same_vardecl(op, vardecl);
|
|
}
|
|
|
|
static bool is_stmt_nocapture_arg(const gcall *stmt, int arg_num)
|
|
{
|
|
tree fndecl;
|
|
|
|
fndecl = gimple_call_fndecl(stmt);
|
|
if (fndecl == NULL_TREE)
|
|
fndecl = gimple_call_fn(stmt);
|
|
|
|
gcc_assert(fndecl != NULL_TREE);
|
|
if (is_fndecl_nocapture_arg(fndecl, arg_num) != NONE_ATTRIBUTE)
|
|
return true;
|
|
|
|
/*
|
|
* These are potentially nocapture functions that must be checked
|
|
* manually.
|
|
*/
|
|
if (print_missing_attr)
|
|
inform(gimple_location(stmt), "nocapture attribute is missing (fn: %E, arg: %u)\n", fndecl, arg_num);
|
|
return false;
|
|
}
|
|
|
|
/* Find the argument position of arg. */
|
|
static int get_arg_num(const gcall *call, const_tree arg)
|
|
{
|
|
int idx;
|
|
|
|
for (idx = 0; idx < (int)gimple_call_num_args(call); idx++) {
|
|
const_tree cur_arg = gimple_call_arg(call, idx);
|
|
|
|
if (cur_arg == arg)
|
|
return idx + 1;
|
|
}
|
|
|
|
debug_tree(arg);
|
|
debug_gimple_stmt(call);
|
|
gcc_unreachable();
|
|
}
|
|
|
|
/* Determine if the variable uses are only in nocapture functions. */
|
|
static bool only_nocapture_call(const_tree decl)
|
|
{
|
|
struct cgraph_edge *e;
|
|
struct cgraph_node *caller;
|
|
bool has_call = false;
|
|
|
|
gcc_assert(TREE_CODE(decl) == VAR_DECL);
|
|
|
|
caller = cgraph_get_node(current_function_decl);
|
|
for (e = caller->callees; e; e = e->next_callee) {
|
|
int idx;
|
|
const gcall *call = as_a_const_gcall(e->call_stmt);
|
|
|
|
for (idx = 0; idx < (int)gimple_call_num_args(call); idx++) {
|
|
const_tree arg = gimple_call_arg(call, idx);
|
|
|
|
if (TREE_CODE(arg) != ADDR_EXPR)
|
|
continue;
|
|
if (TREE_OPERAND(arg, 0) != decl)
|
|
continue;
|
|
|
|
has_call = true;
|
|
if (!is_stmt_nocapture_arg(call, idx + 1))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
gcc_assert(has_call);
|
|
return has_call;
|
|
}
|
|
|
|
/* Determine if all uses of a va_format typed variable are nocapture. */
|
|
static bool is_va_format_use_nocapture(const_tree node)
|
|
{
|
|
const_tree decl, type;
|
|
|
|
if (TREE_CODE(node) != COMPONENT_REF)
|
|
return false;
|
|
|
|
decl = TREE_OPERAND(node, 0);
|
|
type = TREE_TYPE(decl);
|
|
gcc_assert(TREE_CODE(type) == RECORD_TYPE);
|
|
|
|
if (!TYPE_NAME(type) || strcmp(TYPE_NAME_POINTER(type), "va_format"))
|
|
return false;
|
|
|
|
return only_nocapture_call(decl);
|
|
}
|
|
|
|
/* If there is a cast to integer (from const char) then it is a nocapture data flow */
|
|
static bool is_cast_to_integer_type(gassign *assign)
|
|
{
|
|
const_tree lhs_type, lhs;
|
|
|
|
if (!gimple_assign_cast_p(assign))
|
|
return false;
|
|
|
|
lhs = gimple_assign_rhs1(assign);
|
|
lhs_type = TREE_TYPE(lhs);
|
|
return TYPE_MODE(lhs_type) != QImode;
|
|
}
|
|
|
|
/* Search the uses of a return value. */
|
|
static bool is_return_value_captured(gimple_set *visited_defs, const gcall *call)
|
|
{
|
|
tree ret = gimple_call_lhs(call);
|
|
|
|
gcc_assert(ret != NULL_TREE);
|
|
return search_capture_ssa_use(visited_defs, ret);
|
|
}
|
|
|
|
/* Check if arg_num is a nocapture argument. */
|
|
static bool is_call_arg_nocapture(gimple_set *visited_defs, const gcall *call, int arg_num)
|
|
{
|
|
tree fndecl = gimple_call_fndecl(call);
|
|
|
|
if (fndecl == NULL_TREE)
|
|
fndecl = gimple_call_fn(call);
|
|
if (fndecl == NULL_TREE)
|
|
return false;
|
|
|
|
if (is_negative_nocapture_arg(fndecl, -arg_num) && is_return_value_captured(visited_defs, call))
|
|
return false;
|
|
|
|
return is_stmt_nocapture_arg(call, arg_num);
|
|
}
|
|
|
|
/* Determine whether the function has at least one nocapture argument. */
|
|
static bool has_nocapture_param(const_tree fndecl)
|
|
{
|
|
const_tree attr;
|
|
|
|
if (fndecl == NULL_TREE)
|
|
return false;
|
|
|
|
if (is_syscall(fndecl))
|
|
return true;
|
|
|
|
attr = lookup_attribute("nocapture", DECL_ATTRIBUTES(fndecl));
|
|
if (attr == NULL_TREE)
|
|
attr = lookup_attribute("format", TYPE_ATTRIBUTES(TREE_TYPE(fndecl)));
|
|
return attr != NULL_TREE;
|
|
}
|
|
|
|
static void walk_def_stmt(bool *has_capture_use, gimple_set *visited, tree node)
|
|
{
|
|
gimple def_stmt;
|
|
const_tree parm_decl;
|
|
|
|
if (*has_capture_use)
|
|
return;
|
|
|
|
if (TREE_CODE(node) != SSA_NAME)
|
|
goto true_out;
|
|
|
|
parm_decl = SSA_NAME_VAR(node);
|
|
if (parm_decl != NULL_TREE && TREE_CODE(parm_decl) == PARM_DECL)
|
|
return;
|
|
|
|
def_stmt = initify_get_def_stmt(node);
|
|
if (pointer_set_insert(visited, def_stmt))
|
|
return;
|
|
|
|
switch (gimple_code(def_stmt)) {
|
|
case GIMPLE_CALL: {
|
|
tree fndecl = gimple_call_fndecl(def_stmt);
|
|
|
|
if (fndecl == NULL_TREE)
|
|
fndecl = gimple_call_fn(def_stmt);
|
|
|
|
gcc_assert(fndecl != NULL_TREE);
|
|
if (has_nocapture_param(fndecl))
|
|
goto true_out;
|
|
return;
|
|
}
|
|
|
|
case GIMPLE_ASM:
|
|
case GIMPLE_ASSIGN:
|
|
goto true_out;
|
|
|
|
case GIMPLE_NOP:
|
|
return;
|
|
|
|
case GIMPLE_PHI: {
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < gimple_phi_num_args(def_stmt); i++) {
|
|
tree arg = gimple_phi_arg_def(def_stmt, i);
|
|
|
|
walk_def_stmt(has_capture_use, visited, arg);
|
|
}
|
|
return;
|
|
}
|
|
|
|
default:
|
|
debug_gimple_stmt(def_stmt);
|
|
error("%s: unknown gimple code", __func__);
|
|
gcc_unreachable();
|
|
}
|
|
gcc_unreachable();
|
|
|
|
true_out:
|
|
*has_capture_use = true;
|
|
}
|
|
|
|
static bool search_return_capture_use(const greturn *ret_stmt)
|
|
{
|
|
gimple_set *def_visited;
|
|
tree ret;
|
|
bool has_capture_use;
|
|
|
|
if (is_negative_nocapture_arg(current_function_decl, 0))
|
|
return false;
|
|
|
|
def_visited = pointer_set_create();
|
|
ret = gimple_return_retval(ret_stmt);
|
|
has_capture_use = false;
|
|
walk_def_stmt(&has_capture_use, def_visited, ret);
|
|
pointer_set_destroy(def_visited);
|
|
|
|
return has_capture_use;
|
|
}
|
|
|
|
static bool lhs_is_a_nocapture_parm_decl(const_tree lhs)
|
|
{
|
|
int arg_idx, len;
|
|
tree arg_list;
|
|
|
|
if (TREE_CODE(lhs) != PARM_DECL)
|
|
return false;
|
|
|
|
arg_list = DECL_ARGUMENTS(current_function_decl);
|
|
len = list_length(arg_list);
|
|
|
|
for (arg_idx = 0; arg_idx < len; arg_idx++) {
|
|
const_tree arg = chain_index(arg_idx, arg_list);
|
|
|
|
if (arg == lhs)
|
|
return is_fndecl_nocapture_arg(current_function_decl, arg_idx + 1) != NONE_ATTRIBUTE;
|
|
}
|
|
|
|
debug_tree(current_function_decl);
|
|
debug_tree(lhs);
|
|
gcc_unreachable();
|
|
}
|
|
|
|
static void has_capture_use_ssa_var(bool *has_capture_use, gimple_set *visited_defs, tree_set *use_visited, tree node)
|
|
{
|
|
imm_use_iterator imm_iter;
|
|
use_operand_p use_p;
|
|
|
|
if (pointer_set_insert(use_visited, node))
|
|
return;
|
|
|
|
if (*has_capture_use)
|
|
return;
|
|
|
|
if (is_va_format_use_nocapture(node))
|
|
return;
|
|
|
|
if (lhs_is_a_nocapture_parm_decl(node))
|
|
return;
|
|
|
|
if (TREE_CODE(node) != SSA_NAME)
|
|
goto true_out;
|
|
|
|
FOR_EACH_IMM_USE_FAST(use_p, imm_iter, node) {
|
|
gimple use_stmt = USE_STMT(use_p);
|
|
|
|
if (use_stmt == NULL)
|
|
return;
|
|
if (is_gimple_debug(use_stmt))
|
|
continue;
|
|
|
|
if (pointer_set_insert(visited_defs, use_stmt))
|
|
continue;
|
|
|
|
switch (gimple_code(use_stmt)) {
|
|
case GIMPLE_COND:
|
|
case GIMPLE_SWITCH:
|
|
return;
|
|
|
|
case GIMPLE_ASM:
|
|
goto true_out;
|
|
|
|
case GIMPLE_CALL: {
|
|
const gcall *call = as_a_const_gcall(use_stmt);
|
|
int arg_num = get_arg_num(call, node);
|
|
|
|
if (is_call_arg_nocapture(visited_defs, call, arg_num))
|
|
return;
|
|
goto true_out;
|
|
}
|
|
|
|
case GIMPLE_ASSIGN: {
|
|
tree lhs;
|
|
gassign *assign = as_a_gassign(use_stmt);
|
|
const_tree rhs = gimple_assign_rhs1(assign);
|
|
|
|
if (TREE_CODE(rhs) == INDIRECT_REF)
|
|
return;
|
|
#if BUILDING_GCC_VERSION >= 4006
|
|
if (TREE_CODE(rhs) == MEM_REF)
|
|
return;
|
|
#endif
|
|
if (is_cast_to_integer_type(assign))
|
|
return;
|
|
|
|
lhs = gimple_assign_lhs(assign);
|
|
has_capture_use_ssa_var(has_capture_use, visited_defs, use_visited, lhs);
|
|
return;
|
|
}
|
|
|
|
case GIMPLE_PHI: {
|
|
tree result = gimple_phi_result(use_stmt);
|
|
|
|
has_capture_use_ssa_var(has_capture_use, visited_defs, use_visited, result);
|
|
return;
|
|
}
|
|
|
|
case GIMPLE_RETURN:
|
|
if (search_return_capture_use(as_a_const_greturn(use_stmt)))
|
|
goto true_out;
|
|
return;
|
|
|
|
default:
|
|
debug_tree(node);
|
|
debug_gimple_stmt(use_stmt);
|
|
gcc_unreachable();
|
|
}
|
|
}
|
|
return;
|
|
|
|
true_out:
|
|
*has_capture_use = true;
|
|
}
|
|
|
|
static bool search_capture_ssa_use(gimple_set *visited_defs, tree node)
|
|
{
|
|
tree_set *use_visited;
|
|
bool has_capture_use = false;
|
|
|
|
use_visited = tree_pointer_set_create();
|
|
has_capture_use_ssa_var(&has_capture_use, visited_defs, use_visited, node);
|
|
pointer_set_destroy(use_visited);
|
|
|
|
return has_capture_use;
|
|
}
|
|
|
|
static bool search_capture_use(const_tree vardecl, gimple stmt)
|
|
{
|
|
unsigned int i;
|
|
gimple_set *visited_defs = pointer_set_create();
|
|
|
|
for (i = 0; i < gimple_num_ops(stmt); i++) {
|
|
int arg_num;
|
|
tree op = *(gimple_op_ptr(stmt, i));
|
|
|
|
if (op == NULL_TREE)
|
|
continue;
|
|
if (is_gimple_constant(op))
|
|
continue;
|
|
|
|
if (!compare_ops(vardecl, op))
|
|
continue;
|
|
|
|
switch (gimple_code(stmt)) {
|
|
case GIMPLE_COND:
|
|
break;
|
|
|
|
case GIMPLE_ASM:
|
|
gcc_assert(get_init_exit_section(vardecl) == NONE);
|
|
goto true_out;
|
|
|
|
case GIMPLE_CALL:
|
|
if (i == 0)
|
|
break;
|
|
/* return, fndecl */
|
|
gcc_assert(i >= 3);
|
|
arg_num = i - 2;
|
|
|
|
if (is_call_arg_nocapture(visited_defs, as_a_const_gcall(stmt), arg_num))
|
|
break;
|
|
goto true_out;
|
|
|
|
case GIMPLE_ASSIGN: {
|
|
tree lhs;
|
|
const_tree rhs = gimple_assign_rhs1(stmt);
|
|
|
|
if (TREE_CODE(rhs) == INDIRECT_REF)
|
|
break;
|
|
#if BUILDING_GCC_VERSION >= 4006
|
|
if (TREE_CODE(rhs) == MEM_REF)
|
|
break;
|
|
#endif
|
|
|
|
lhs = gimple_assign_lhs(stmt);
|
|
if (lhs_is_a_nocapture_parm_decl(lhs))
|
|
break;
|
|
|
|
if (!search_capture_ssa_use(visited_defs, lhs))
|
|
break;
|
|
gcc_assert(get_init_exit_section(vardecl) == NONE);
|
|
goto true_out;
|
|
}
|
|
|
|
case GIMPLE_RETURN:
|
|
if (search_return_capture_use(as_a_const_greturn(stmt)))
|
|
goto true_out;
|
|
break;
|
|
default:
|
|
debug_tree(vardecl);
|
|
debug_gimple_stmt(stmt);
|
|
gcc_unreachable();
|
|
}
|
|
}
|
|
|
|
pointer_set_destroy(visited_defs);
|
|
return false;
|
|
|
|
true_out:
|
|
pointer_set_destroy(visited_defs);
|
|
return true;
|
|
|
|
}
|
|
|
|
/* Check all initialized local variables for nocapture uses. */
|
|
static bool is_in_capture_init(const_tree vardecl)
|
|
{
|
|
unsigned int i __unused;
|
|
tree var;
|
|
|
|
if (TREE_CODE(vardecl) == PARM_DECL)
|
|
return false;
|
|
|
|
FOR_EACH_LOCAL_DECL(cfun, i, var) {
|
|
const_tree type, initial = DECL_INITIAL(var);
|
|
|
|
if (DECL_EXTERNAL(var))
|
|
continue;
|
|
if (initial == NULL_TREE)
|
|
continue;
|
|
if (TREE_CODE(initial) != CONSTRUCTOR)
|
|
continue;
|
|
|
|
type = TREE_TYPE(var);
|
|
gcc_assert(TREE_CODE(type) == RECORD_TYPE || DECL_P(var));
|
|
if (check_constructor(initial, vardecl))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool has_capture_use_local_var(const_tree vardecl)
|
|
{
|
|
basic_block bb;
|
|
enum tree_code code = TREE_CODE(vardecl);
|
|
|
|
gcc_assert(code == VAR_DECL || code == PARM_DECL);
|
|
|
|
if (is_in_capture_init(vardecl))
|
|
return true;
|
|
|
|
FOR_EACH_BB_FN(bb, cfun) {
|
|
gimple_stmt_iterator gsi;
|
|
|
|
for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
|
|
if (search_capture_use(vardecl, gsi_stmt(gsi)))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Search local variables that have only nocapture uses. */
|
|
static void find_local_str(void)
|
|
{
|
|
unsigned int i __unused;
|
|
tree var;
|
|
|
|
FOR_EACH_LOCAL_DECL(cfun, i, var) {
|
|
tree str, init_val, asm_name;
|
|
|
|
if (TREE_CODE(TREE_TYPE(var)) != ARRAY_TYPE)
|
|
continue;
|
|
|
|
init_val = DECL_INITIAL(var);
|
|
if (init_val == NULL_TREE || init_val == error_mark_node)
|
|
continue;
|
|
if (TREE_CODE(init_val) != STRING_CST)
|
|
continue;
|
|
|
|
asm_name = DECL_ASSEMBLER_NAME(var);
|
|
if (asm_name != NULL_TREE && TREE_SYMBOL_REFERENCED(asm_name))
|
|
continue;
|
|
|
|
if (has_capture_use_local_var(var))
|
|
continue;
|
|
|
|
str = get_string_cst(init_val);
|
|
gcc_assert(str);
|
|
|
|
if (set_init_exit_section(var) && verbose)
|
|
inform(DECL_SOURCE_LOCATION(var), "initified local var: %s: %s", DECL_NAME_POINTER(current_function_decl), TREE_STRING_POINTER(str));
|
|
}
|
|
}
|
|
|
|
static tree create_decl(tree node)
|
|
{
|
|
tree str, decl, type, name, type_type;
|
|
location_t loc;
|
|
|
|
str = get_string_cst(node);
|
|
type = TREE_TYPE(str);
|
|
gcc_assert(TREE_CODE(type) == ARRAY_TYPE);
|
|
|
|
type_type = TREE_TYPE(type);
|
|
gcc_assert(type_type != NULL_TREE && TREE_CODE(type_type) == INTEGER_TYPE);
|
|
|
|
name = create_tmp_var_name("initify");
|
|
loc = DECL_SOURCE_LOCATION(current_function_decl);
|
|
decl = build_decl(loc, VAR_DECL, name, type);
|
|
|
|
DECL_INITIAL(decl) = str;
|
|
DECL_CONTEXT(decl) = current_function_decl;
|
|
DECL_ARTIFICIAL(decl) = 1;
|
|
|
|
TREE_STATIC(decl) = 1;
|
|
TREE_READONLY(decl) = 1;
|
|
TREE_ADDRESSABLE(decl) = 1;
|
|
TREE_USED(decl) = 1;
|
|
|
|
add_referenced_var(decl);
|
|
add_local_decl(cfun, decl);
|
|
|
|
varpool_add_new_variable(decl);
|
|
varpool_mark_needed_node(varpool_node(decl));
|
|
|
|
DECL_CHAIN(decl) = BLOCK_VARS(DECL_INITIAL(current_function_decl));
|
|
BLOCK_VARS(DECL_INITIAL(current_function_decl)) = decl;
|
|
|
|
return build_fold_addr_expr_loc(loc, decl);
|
|
}
|
|
|
|
static void set_section_call_assign(gimple stmt, tree node, unsigned int num)
|
|
{
|
|
tree decl;
|
|
|
|
decl = create_decl(node);
|
|
|
|
switch (gimple_code(stmt)) {
|
|
case GIMPLE_ASSIGN:
|
|
gcc_assert(gimple_num_ops(stmt) == 2);
|
|
gimple_assign_set_rhs1(stmt, decl);
|
|
break;
|
|
|
|
case GIMPLE_CALL:
|
|
gimple_call_set_arg(stmt, num, decl);
|
|
break;
|
|
|
|
default:
|
|
debug_gimple_stmt(stmt);
|
|
error("%s: unknown gimple code", __func__);
|
|
gcc_unreachable();
|
|
}
|
|
|
|
update_stmt(stmt);
|
|
|
|
if (set_init_exit_section(TREE_OPERAND(decl, 0)) && verbose)
|
|
inform(gimple_location(stmt), "initified function arg: %E: [%E]", current_function_decl, get_string_cst(node));
|
|
}
|
|
|
|
static tree initify_create_new_var(tree type)
|
|
{
|
|
tree new_var = create_tmp_var(type, "initify");
|
|
|
|
add_referenced_var(new_var);
|
|
mark_sym_for_renaming(new_var);
|
|
return new_var;
|
|
}
|
|
|
|
static void initify_create_new_phi_arg(gimple_set *visited_defs, tree ssa_var, gphi *stmt, unsigned int i)
|
|
{
|
|
gassign *assign;
|
|
gimple_stmt_iterator gsi;
|
|
basic_block arg_bb;
|
|
tree decl, arg;
|
|
const_tree str;
|
|
location_t loc;
|
|
|
|
arg = gimple_phi_arg_def(stmt, i);
|
|
|
|
if (search_capture_ssa_use(visited_defs, arg))
|
|
return;
|
|
|
|
decl = create_decl(arg);
|
|
|
|
assign = gimple_build_assign(ssa_var, decl);
|
|
|
|
arg_bb = gimple_phi_arg_edge(stmt, i)->src;
|
|
gcc_assert(arg_bb->index != 0);
|
|
|
|
gsi = gsi_after_labels(arg_bb);
|
|
gsi_insert_before(&gsi, assign, GSI_NEW_STMT);
|
|
update_stmt(assign);
|
|
|
|
if (!set_init_exit_section(TREE_OPERAND(decl, 0)) || !verbose)
|
|
return;
|
|
|
|
loc = gimple_location(stmt);
|
|
str = get_string_cst(arg);
|
|
inform(loc, "initified local var, phi arg: %E: [%E]", current_function_decl, str);
|
|
}
|
|
|
|
static void set_section_phi(bool *has_str_cst, gimple_set *visited, gphi *stmt)
|
|
{
|
|
tree result, ssa_var;
|
|
unsigned int i;
|
|
|
|
result = gimple_phi_result(stmt);
|
|
ssa_var = initify_create_new_var(TREE_TYPE(result));
|
|
|
|
for (i = 0; i < gimple_phi_num_args(stmt); i++) {
|
|
tree arg = gimple_phi_arg_def(stmt, i);
|
|
|
|
if (get_string_cst(arg) == NULL_TREE)
|
|
search_constant_strings(has_str_cst, visited, arg);
|
|
else
|
|
initify_create_new_phi_arg(visited, ssa_var, stmt, i);
|
|
}
|
|
}
|
|
|
|
static void search_constant_strings(bool *has_str_cst, gimple_set *visited, tree node)
|
|
{
|
|
gimple def_stmt;
|
|
const_tree parm_decl;
|
|
|
|
if (!*has_str_cst)
|
|
return;
|
|
|
|
if (TREE_CODE(node) != SSA_NAME)
|
|
goto false_out;
|
|
|
|
parm_decl = SSA_NAME_VAR(node);
|
|
if (parm_decl != NULL_TREE && TREE_CODE(parm_decl) == PARM_DECL)
|
|
goto false_out;
|
|
|
|
def_stmt = initify_get_def_stmt(node);
|
|
if (pointer_set_insert(visited, def_stmt))
|
|
return;
|
|
|
|
switch (gimple_code(def_stmt)) {
|
|
case GIMPLE_NOP:
|
|
case GIMPLE_CALL:
|
|
case GIMPLE_ASM:
|
|
case GIMPLE_RETURN:
|
|
goto false_out;
|
|
|
|
case GIMPLE_PHI:
|
|
set_section_phi(has_str_cst, visited, as_a_gphi(def_stmt));
|
|
return;
|
|
|
|
case GIMPLE_ASSIGN: {
|
|
tree rhs1, str;
|
|
|
|
if (gimple_num_ops(def_stmt) != 2)
|
|
goto false_out;
|
|
|
|
rhs1 = gimple_assign_rhs1(def_stmt);
|
|
search_constant_strings(has_str_cst, visited, rhs1);
|
|
if (!*has_str_cst)
|
|
return;
|
|
|
|
if (search_capture_ssa_use(visited, node))
|
|
goto false_out;
|
|
|
|
str = get_string_cst(rhs1);
|
|
gcc_assert(str != NULL_TREE);
|
|
set_section_call_assign(def_stmt, rhs1, 0);
|
|
return;
|
|
}
|
|
|
|
default:
|
|
debug_gimple_stmt(def_stmt);
|
|
error("%s: unknown gimple code", __func__);
|
|
gcc_unreachable();
|
|
}
|
|
gcc_unreachable();
|
|
|
|
false_out:
|
|
*has_str_cst = false;
|
|
}
|
|
|
|
/* Search constant strings assigned to variables. */
|
|
static void search_var_param(gcall *stmt)
|
|
{
|
|
int num;
|
|
gimple_set *visited = pointer_set_create();
|
|
|
|
pointer_set_insert(visited, stmt);
|
|
|
|
for (num = 0; num < (int)gimple_call_num_args(stmt); num++) {
|
|
const_tree type, fndecl;
|
|
bool has_str_cst = true;
|
|
tree str, arg = gimple_call_arg(stmt, num);
|
|
|
|
str = get_string_cst(arg);
|
|
if (str != NULL_TREE)
|
|
continue;
|
|
|
|
if (TREE_CODE(TREE_TYPE(arg)) != POINTER_TYPE)
|
|
continue;
|
|
type = TREE_TYPE(TREE_TYPE(arg));
|
|
if (!TYPE_STRING_FLAG(type))
|
|
continue;
|
|
|
|
fndecl = gimple_call_fndecl(stmt);
|
|
if (is_negative_nocapture_arg(fndecl, -(num + 1)) && is_return_value_captured(visited, stmt))
|
|
continue;
|
|
|
|
if (is_fndecl_nocapture_arg(fndecl, num + 1) != NONE_ATTRIBUTE)
|
|
search_constant_strings(&has_str_cst, visited, arg);
|
|
}
|
|
|
|
pointer_set_destroy(visited);
|
|
}
|
|
|
|
/* Search constant strings passed as arguments. */
|
|
static void search_str_param(gcall *stmt)
|
|
{
|
|
int num;
|
|
gimple_set *visited = pointer_set_create();
|
|
|
|
pointer_set_insert(visited, stmt);
|
|
|
|
for (num = 0; num < (int)gimple_call_num_args(stmt); num++) {
|
|
const_tree fndecl;
|
|
tree str, arg = gimple_call_arg(stmt, num);
|
|
|
|
str = get_string_cst(arg);
|
|
if (str == NULL_TREE)
|
|
continue;
|
|
|
|
fndecl = gimple_call_fndecl(stmt);
|
|
if (is_negative_nocapture_arg(fndecl, -(num + 1)) && is_return_value_captured(visited, stmt))
|
|
continue;
|
|
|
|
if (is_fndecl_nocapture_arg(fndecl, num + 1) != NONE_ATTRIBUTE)
|
|
set_section_call_assign(stmt, arg, num);
|
|
}
|
|
|
|
pointer_set_destroy(visited);
|
|
}
|
|
|
|
/* Search constant strings in arguments of nocapture functions. */
|
|
static void search_const_strs(void)
|
|
{
|
|
basic_block bb;
|
|
|
|
FOR_EACH_BB_FN(bb, cfun) {
|
|
gimple_stmt_iterator gsi;
|
|
|
|
for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
|
|
gcall *call_stmt;
|
|
gimple stmt = gsi_stmt(gsi);
|
|
|
|
if (!is_gimple_call(stmt))
|
|
continue;
|
|
|
|
call_stmt = as_a_gcall(stmt);
|
|
if (!has_nocapture_param(gimple_call_fndecl(call_stmt)))
|
|
continue;
|
|
search_str_param(call_stmt);
|
|
search_var_param(call_stmt);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Verify the data flows of the uses of function arguments marked by the nocapture attribute.
|
|
* The printf attribute is ignored temporarily.
|
|
*/
|
|
static void verify_nocapture_functions(void)
|
|
{
|
|
int i, len;
|
|
tree arg_list;
|
|
|
|
if (disable_verify_nocapture_functions)
|
|
return;
|
|
|
|
if (is_syscall(current_function_decl))
|
|
return;
|
|
|
|
if (!has_nocapture_param(current_function_decl))
|
|
return;
|
|
|
|
arg_list = DECL_ARGUMENTS(current_function_decl);
|
|
len = list_length(arg_list);
|
|
for (i = 0; i < len; i++) {
|
|
const_tree arg;
|
|
|
|
if (is_fndecl_nocapture_arg(current_function_decl, i + 1) != NOCAPTURE)
|
|
continue;
|
|
|
|
arg = chain_index(i, arg_list);
|
|
gcc_assert(arg != NULL_TREE);
|
|
|
|
if (has_capture_use_local_var(arg))
|
|
warning(0, "%qE captures its %u (%qD) parameter, please remove it from the nocapture attribute.", current_function_decl, i + 1, arg);
|
|
}
|
|
}
|
|
|
|
/* Find and move constant strings to the proper init or exit read-only data section. */
|
|
static unsigned int initify_function_transform(struct cgraph_node *node __unused)
|
|
{
|
|
verify_nocapture_functions();
|
|
|
|
if (get_init_exit_section(current_function_decl) == NONE)
|
|
return 0;
|
|
|
|
find_local_str();
|
|
search_const_strs();
|
|
|
|
return TODO_dump_func | TODO_verify_ssa | TODO_verify_stmts
|
|
| TODO_remove_unused_locals | TODO_cleanup_cfg
|
|
| TODO_ggc_collect | TODO_verify_flow | TODO_update_ssa;
|
|
}
|
|
|
|
static void __unused debug_print_section_type(struct cgraph_node *node)
|
|
{
|
|
enum section_type section;
|
|
|
|
section = (enum section_type)(unsigned long)NODE_SYMBOL(node)->aux;
|
|
switch (section) {
|
|
case INIT:
|
|
fprintf(stderr, "init\n");
|
|
break;
|
|
|
|
case EXIT:
|
|
fprintf(stderr, "exit\n");
|
|
break;
|
|
|
|
case BOTH:
|
|
fprintf(stderr, "init and exit\n");
|
|
break;
|
|
|
|
case NONE:
|
|
fprintf(stderr, "none\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool has_non_init_caller(struct cgraph_node *callee)
|
|
{
|
|
struct cgraph_edge *e = callee->callers;
|
|
|
|
if (!e)
|
|
return true;
|
|
|
|
for (; e; e = e->next_caller) {
|
|
enum section_type caller_section;
|
|
struct cgraph_node *caller = e->caller;
|
|
|
|
caller_section = get_init_exit_section(NODE_DECL(caller));
|
|
if (caller_section == NONE && NODE_SYMBOL(caller)->aux == (void *)NONE)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool has_non_init_clone(cgraph_set *visited, struct cgraph_node *node)
|
|
{
|
|
if (!node)
|
|
return false;
|
|
|
|
if (pointer_set_insert(visited, node))
|
|
return false;
|
|
|
|
if (has_non_init_caller(node))
|
|
return true;
|
|
|
|
if (has_non_init_clone(visited, node->clones))
|
|
return true;
|
|
|
|
return has_non_init_clone(visited, node->clone_of);
|
|
}
|
|
|
|
/*
|
|
* If the function is called by only __init/__exit functions then it can become
|
|
* an __init/__exit function as well.
|
|
*/
|
|
static bool should_init_exit(struct cgraph_node *callee)
|
|
{
|
|
cgraph_set *visited;
|
|
bool has_non_init;
|
|
const_tree callee_decl = NODE_DECL(callee);
|
|
|
|
if (NODE_SYMBOL(callee)->aux != (void *)NONE)
|
|
return false;
|
|
if (get_init_exit_section(callee_decl) != NONE)
|
|
return false;
|
|
|
|
/* If gcc isn't in LTO mode then we can handle only static functions. */
|
|
if (!in_lto_p && TREE_PUBLIC(callee_decl))
|
|
return false;
|
|
|
|
if (NODE_SYMBOL(callee)->address_taken)
|
|
return false;
|
|
|
|
visited = cgraph_pointer_set_create();
|
|
has_non_init = has_non_init_clone(visited, callee);
|
|
pointer_set_destroy(visited);
|
|
|
|
return !has_non_init;
|
|
}
|
|
|
|
static bool inherit_section(struct cgraph_node *callee, struct cgraph_node *caller, enum section_type caller_section)
|
|
{
|
|
enum section_type callee_section;
|
|
|
|
if (caller_section == NONE)
|
|
caller_section = (enum section_type)(unsigned long)NODE_SYMBOL(caller)->aux;
|
|
|
|
callee_section = (enum section_type)(unsigned long)NODE_SYMBOL(callee)->aux;
|
|
if (caller_section == INIT && callee_section == EXIT)
|
|
goto both_section;
|
|
|
|
if (caller_section == EXIT && callee_section == INIT)
|
|
goto both_section;
|
|
|
|
if (caller_section == BOTH && (callee_section == INIT || callee_section == EXIT))
|
|
goto both_section;
|
|
|
|
if (!should_init_exit(callee))
|
|
return false;
|
|
|
|
gcc_assert(callee_section == NONE);
|
|
NODE_SYMBOL(callee)->aux = (void *)caller_section;
|
|
return true;
|
|
|
|
both_section:
|
|
NODE_SYMBOL(callee)->aux = (void *)BOTH;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Try to propagate __init/__exit to callees in __init/__exit functions.
|
|
* If a function is called by __init and __exit functions as well then it can be
|
|
* an __exit function at most.
|
|
*/
|
|
static bool search_init_exit_callers(void)
|
|
{
|
|
struct cgraph_node *node;
|
|
bool change = false;
|
|
|
|
FOR_EACH_FUNCTION(node) {
|
|
struct cgraph_edge *e;
|
|
enum section_type section;
|
|
const_tree cur_fndecl = NODE_DECL(node);
|
|
|
|
if (DECL_BUILT_IN(cur_fndecl))
|
|
continue;
|
|
|
|
section = get_init_exit_section(cur_fndecl);
|
|
if (section == NONE && NODE_SYMBOL(node)->aux == (void *)NONE)
|
|
continue;
|
|
|
|
for (e = node->callees; e; e = e->next_callee) {
|
|
if (e->callee->global.inlined_to)
|
|
continue;
|
|
|
|
if (inherit_section(e->callee, node, section))
|
|
change = true;
|
|
}
|
|
}
|
|
|
|
return change;
|
|
}
|
|
|
|
/* We can't move functions to the init/exit sections from certain sections. */
|
|
static bool can_move_to_init_exit(const_tree fndecl)
|
|
{
|
|
const char *section_name = get_decl_section_name(fndecl);
|
|
|
|
if (!section_name)
|
|
return true;
|
|
|
|
if (!strcmp(section_name, ".ref.text"))
|
|
return false;
|
|
|
|
if (!strcmp(section_name, ".meminit.text"))
|
|
return false;
|
|
|
|
inform(DECL_SOURCE_LOCATION(fndecl), "Section of %qE: %s\n", fndecl, section_name);
|
|
gcc_unreachable();
|
|
}
|
|
|
|
static void move_function_to_init_exit_text(struct cgraph_node *node)
|
|
{
|
|
const char *section_name;
|
|
tree id, attr;
|
|
tree section_str, attr_args, fndecl = NODE_DECL(node);
|
|
|
|
/*
|
|
* If the function is a candidate for both __init and __exit and enable_init_to_exit_moves is false
|
|
* then these functions arent't moved to the exit section.
|
|
*/
|
|
if (NODE_SYMBOL(node)->aux == (void *)BOTH) {
|
|
if (enable_init_to_exit_moves)
|
|
NODE_SYMBOL(node)->aux = (void *)EXIT;
|
|
else
|
|
return;
|
|
}
|
|
|
|
if (NODE_SYMBOL(node)->aux == (void *)NONE)
|
|
return;
|
|
|
|
if (!can_move_to_init_exit(fndecl))
|
|
return;
|
|
|
|
if (verbose) {
|
|
const char *attr_name;
|
|
location_t loc = DECL_SOURCE_LOCATION(fndecl);
|
|
|
|
attr_name = NODE_SYMBOL(node)->aux == (void *)INIT ? "__init" : "__exit";
|
|
|
|
if (in_lto_p && TREE_PUBLIC(fndecl))
|
|
inform(loc, "%s attribute is missing from the %qE function (public)", attr_name, fndecl);
|
|
|
|
if (!in_lto_p && !TREE_PUBLIC(fndecl))
|
|
inform(loc, "%s attribute is missing from the %qE function (static)", attr_name, fndecl);
|
|
}
|
|
|
|
if (in_lto_p)
|
|
return;
|
|
|
|
/* Add the init/exit section attribute to the function declaration. */
|
|
DECL_ATTRIBUTES(fndecl) = copy_list(DECL_ATTRIBUTES(fndecl));
|
|
|
|
section_name = NODE_SYMBOL(node)->aux == (void *)INIT ? ".init.text" : ".exit.text";
|
|
section_str = build_const_char_string(strlen(section_name) + 1, section_name);
|
|
TREE_READONLY(section_str) = 1;
|
|
TREE_STATIC(section_str) = 1;
|
|
attr_args = build_tree_list(NULL_TREE, section_str);
|
|
|
|
id = get_identifier("__section__");
|
|
attr = DECL_ATTRIBUTES(fndecl);
|
|
DECL_ATTRIBUTES(fndecl) = tree_cons(id, attr_args, attr);
|
|
|
|
#if BUILDING_GCC_VERSION < 5000
|
|
DECL_SECTION_NAME(fndecl) = section_str;
|
|
#endif
|
|
set_decl_section_name(fndecl, section_name);
|
|
}
|
|
|
|
/* Find all functions that can become __init/__exit functions */
|
|
static unsigned int initify_execute(void)
|
|
{
|
|
struct cgraph_node *node;
|
|
|
|
if (!search_init_exit_functions)
|
|
return 0;
|
|
|
|
if (flag_lto && !in_lto_p)
|
|
return 0;
|
|
|
|
FOR_EACH_FUNCTION(node)
|
|
NODE_SYMBOL(node)->aux = (void *)NONE;
|
|
|
|
while (search_init_exit_callers()) {};
|
|
|
|
FOR_EACH_FUNCTION(node) {
|
|
move_function_to_init_exit_text(node);
|
|
|
|
NODE_SYMBOL(node)->aux = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define PASS_NAME initify
|
|
#define NO_WRITE_SUMMARY
|
|
#define NO_GENERATE_SUMMARY
|
|
#define NO_READ_SUMMARY
|
|
#define NO_READ_OPTIMIZATION_SUMMARY
|
|
#define NO_WRITE_OPTIMIZATION_SUMMARY
|
|
#define NO_STMT_FIXUP
|
|
#define NO_VARIABLE_TRANSFORM
|
|
#define NO_GATE
|
|
|
|
#include "gcc-generate-ipa-pass.h"
|
|
|
|
static unsigned int (*old_section_type_flags)(tree decl, const char *name, int reloc);
|
|
|
|
static unsigned int initify_section_type_flags(tree decl, const char *name, int reloc)
|
|
{
|
|
if (!strcmp(name, ".init.rodata.str") || !strcmp(name, ".exit.rodata.str")) {
|
|
gcc_assert(TREE_CODE(decl) == VAR_DECL);
|
|
gcc_assert(DECL_INITIAL(decl));
|
|
gcc_assert(TREE_CODE(DECL_INITIAL(decl)) == STRING_CST);
|
|
|
|
return 1 | SECTION_MERGE | SECTION_STRINGS;
|
|
}
|
|
|
|
return old_section_type_flags(decl, name, reloc);
|
|
}
|
|
|
|
static void initify_start_unit(void __unused *gcc_data, void __unused *user_data)
|
|
{
|
|
old_section_type_flags = targetm.section_type_flags;
|
|
targetm.section_type_flags = initify_section_type_flags;
|
|
}
|
|
|
|
__visible int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version *version)
|
|
{
|
|
int i;
|
|
const int argc = plugin_info->argc;
|
|
bool enabled = true;
|
|
const struct plugin_argument * const argv = plugin_info->argv;
|
|
const char * const plugin_name = plugin_info->base_name;
|
|
|
|
PASS_INFO(initify, "inline", 1, PASS_POS_INSERT_AFTER);
|
|
|
|
if (!plugin_default_version_check(version, &gcc_version)) {
|
|
error_gcc_version(version);
|
|
return 1;
|
|
}
|
|
|
|
for (i = 0; i < argc; ++i) {
|
|
if (!(strcmp(argv[i].key, "disable"))) {
|
|
enabled = false;
|
|
continue;
|
|
}
|
|
if (!strcmp(argv[i].key, "verbose")) {
|
|
verbose = true;
|
|
continue;
|
|
}
|
|
if (!strcmp(argv[i].key, "print_missing_attr")) {
|
|
print_missing_attr = true;
|
|
continue;
|
|
}
|
|
if (!strcmp(argv[i].key, "search_init_exit_functions")) {
|
|
search_init_exit_functions = true;
|
|
continue;
|
|
}
|
|
if (!strcmp(argv[i].key, "enable_init_to_exit_moves")) {
|
|
enable_init_to_exit_moves = true;
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(argv[i].key, "disable_verify_nocapture_functions")) {
|
|
disable_verify_nocapture_functions = true;
|
|
continue;
|
|
}
|
|
|
|
error(G_("unknown option '-fplugin-arg-%s-%s'"), plugin_name, argv[i].key);
|
|
}
|
|
|
|
register_callback(plugin_name, PLUGIN_INFO, NULL, &initify_plugin_info);
|
|
if (enabled) {
|
|
register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &initify_pass_info);
|
|
register_callback(plugin_name, PLUGIN_START_UNIT, initify_start_unit, NULL);
|
|
}
|
|
register_callback(plugin_name, PLUGIN_ATTRIBUTES, register_attributes, NULL);
|
|
|
|
return 0;
|
|
}
|