/* * Copyright 2015-2017 by Emese Revfy * 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 . * * 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 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; } static inline void pointer_set_destroy(gimple_set *visited) { delete visited; } typedef struct hash_set 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; } static inline void pointer_set_destroy(tree_set *visited) { delete visited; } typedef struct hash_set 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; } 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; }