From 83045e6ab5f34873af07ce6a9defd4dd7c9a1f8f Mon Sep 17 00:00:00 2001 From: huzife <634763349@qq.com> Date: Tue, 20 May 2025 17:34:43 +0800 Subject: [PATCH] Add alignment-propagation, localize-array, array-dse --- gcc/Makefile.in | 3 + gcc/common.opt | 12 + gcc/config/aarch64/aarch64.cc | 4 + gcc/ipa-alignment-propagation.cc | 478 ++++ gcc/ipa-array-dse.cc | 3317 ++++++++++++++++++++++ gcc/ipa-array-dse.h | 263 ++ gcc/ipa-localize-array.cc | 614 ++++ gcc/ipa-struct-reorg/ipa-struct-reorg.cc | 37 +- gcc/ipa-utils.cc | 44 + gcc/ipa-utils.h | 15 + gcc/passes.def | 14 + gcc/timevar.def | 3 + gcc/tree-pass.h | 3 + 13 files changed, 4806 insertions(+), 1 deletion(-) create mode 100644 gcc/ipa-alignment-propagation.cc create mode 100644 gcc/ipa-array-dse.cc create mode 100644 gcc/ipa-array-dse.h create mode 100644 gcc/ipa-localize-array.cc diff --git a/gcc/Makefile.in b/gcc/Makefile.in index c7a503235f7..070e5e45604 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1451,6 +1451,9 @@ OBJS = \ inchash.o \ incpath.o \ init-regs.o \ + ipa-alignment-propagation.o \ + ipa-localize-array.o \ + ipa-array-dse.o \ ipa-hardware-detection.o \ internal-fn.o \ ipa-struct-reorg/ipa-struct-reorg.o \ diff --git a/gcc/common.opt b/gcc/common.opt index 4cd2574e4cc..ed4696b7ada 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -2069,6 +2069,18 @@ fipa-matrix-reorg Common Ignore Does nothing. Preserved for backward compatibility. +fipa-alignment-propagation +Common Var(flag_ipa_alignment_propagation) Init(0) Optimization +Propagate alignment of local variable's address + +fipa-localize-array +Common Var(flag_ipa_localize_array) Init(0) Optimization +Transform global calloced array to be specific function local. + +fipa-array-dse +Common Var(flag_ipa_array_dse) Init(0) Optimization +Array dead and redundant store elimination. + fipa-reorder-fields Common Var(flag_ipa_reorder_fields) Init(0) Optimization Perform structure fields reorder optimizations. diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc index 80242ddf8be..faa445e2621 100644 --- a/gcc/config/aarch64/aarch64.cc +++ b/gcc/config/aarch64/aarch64.cc @@ -19090,6 +19090,9 @@ extern bool lang_c_p (void); static void override_C_optimize_options (struct gcc_options *opts) { + opts->x_flag_ipa_alignment_propagation = 1; + opts->x_flag_ipa_localize_array = 1; + opts->x_flag_ipa_array_dse = 1; opts->x_flag_ipa_reorder_fields = 1; opts->x_flag_ipa_struct_reorg = 5; opts->x_struct_layout_optimize_level = 5; @@ -19154,6 +19157,7 @@ override_CPP_optimize_options (struct gcc_options *opts) opts->x_param_inline_unit_growth = 256; opts->x_flag_cmlt_arith = 1; opts->x_flag_if_conversion_gimple = 1; + opts->x_flag_find_with_sve = 1; } static void diff --git a/gcc/ipa-alignment-propagation.cc b/gcc/ipa-alignment-propagation.cc new file mode 100644 index 00000000000..3f14818ae9b --- /dev/null +++ b/gcc/ipa-alignment-propagation.cc @@ -0,0 +1,478 @@ +/* Copyright (C) 2019-2022 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tm.h" +#include "tree.h" +#include "tree-cfg.h" +#include "tree-pass.h" +#include "tm_p.h" +#include "basic-block.h" +#include "bitmap.h" +#include "function.h" +#include "cfg.h" +#include "cgraph.h" +#include "gimple.h" +#include "gimple-iterator.h" +#include "gimple-pretty-print.h" +#include "gimple-ssa.h" +#include "ipa-utils.h" + +class alignment_propagator +{ +public: + alignment_propagator (cgraph_node *node); + + void execute (); + +private: + void propagate_params_alignment (); + void transform (); + size_t get_param_alignment (unsigned param_index); + size_t get_var_alignment (tree var); + bool check_assign (gimple *stmt, auto_vec &worklist, + size_t &alignment); + bool check_param (tree t, auto_vec &worklist, size_t &alignment); + int get_param_index_from_ssa (tree var); + size_t get_arg_alignment (cgraph_node *caller, tree arg); + size_t new_alignment (size_t orig, size_t new_value); + bool pow2_or_zerop (size_t value); + size_t abs_value (tree t); + bool candidate_stmt_p (gimple *stmt); + +private: + cgraph_node *node = nullptr; + hash_map alignment_map; +}; + +alignment_propagator::alignment_propagator (cgraph_node *node) + : node (node) +{ +} + +void +alignment_propagator::execute () +{ + if (dump_file) + { + fprintf (dump_file, "Start to rewrite function: %s\n", + node->dump_asm_name()); + dump_function_to_file (node->decl, dump_file, dump_flags); + } + + cfun_saver save (node); + + propagate_params_alignment (); + + /* If no alignment is propagated, there is no need to continue cause + the rest cases are covered by constant propagation. */ + if (!alignment_map.is_empty ()) + transform (); +} + +void +alignment_propagator::propagate_params_alignment () +{ + unsigned i = 0; + tree param = DECL_ARGUMENTS (node->decl); + while (param) + { + size_t alignment = get_param_alignment (i); + if (alignment) + alignment_map.put (param, alignment); + + param = DECL_CHAIN (param); + i++; + } +} + +void +alignment_propagator::transform () +{ + basic_block bb; + FOR_EACH_BB_FN (bb, cfun) + { + for (auto gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + gimple *stmt = gsi_stmt (gsi); + if (!candidate_stmt_p (stmt)) + continue; + + if (dump_file) + { + fprintf (dump_file, "Rewrite stmt:\n "); + print_gimple_stmt (dump_file, stmt, 0, TDF_NONE); + } + + tree lhs = gimple_assign_lhs (stmt); + tree new_rhs = build_int_cst (TREE_TYPE (lhs), 0); + gimple_assign_set_rhs_from_tree (&gsi, new_rhs); + update_stmt (stmt); + + if (dump_file) + { + fprintf (dump_file, "To:\n "); + print_gimple_stmt (dump_file, stmt, 0, TDF_NONE); + fprintf (dump_file, "\n"); + } + } + } +} + +size_t +alignment_propagator::get_param_alignment (unsigned param_index) +{ + size_t alignment = 0; + for (auto e = node->callers; e; e = e->next_caller) + { + if (e->caller == node) + continue; + + if (gimple_call_num_args (e->call_stmt) <= param_index) + return 0; + + tree arg = gimple_call_arg (e->call_stmt, param_index); + size_t arg_alignment = get_arg_alignment (e->caller, arg); + if (!arg_alignment) + return 0; + + if (!alignment || arg_alignment < alignment) + alignment = arg_alignment; + } + + return alignment; +} + +size_t +alignment_propagator::get_var_alignment (tree var) +{ + size_t alignment = 0; + + auto_bitmap visited; + auto_vec worklist; + worklist.safe_push (var); + + while (!worklist.is_empty ()) + { + tree t = worklist.pop (); + if (TREE_CODE (t) == INTEGER_CST) + { + size_t value = abs_value (t); + if (!pow2_or_zerop (value)) + return 0; + + alignment = new_alignment (alignment, value); + continue; + } + + if (TREE_CODE (t) != SSA_NAME) + return 0; + + if (!bitmap_set_bit (visited, SSA_NAME_VERSION (t))) + continue; + + gimple *stmt = SSA_NAME_DEF_STMT (t); + switch (gimple_code (stmt)) + { + case GIMPLE_PHI: + for (unsigned i = 0; i < gimple_phi_num_args (stmt); i++) + worklist.safe_push (gimple_phi_arg_def (stmt, i)); + break; + case GIMPLE_ASSIGN: + if (!check_assign (stmt, worklist, alignment)) + return 0; + break; + case GIMPLE_NOP: + /* If we reach a default def, try to get the argument's alignment + from caller node. */ + if (!check_param (t, worklist, alignment)) + return 0; + break; + default: + return 0; + } + } + + return alignment; +} + +bool +alignment_propagator::check_assign (gimple *stmt, auto_vec &worklist, + size_t &alignment) +{ + if (gimple_assign_single_p (stmt) || gimple_assign_cast_p (stmt)) + { + worklist.safe_push (gimple_assign_rhs1 (stmt)); + return true; + } + + switch (gimple_assign_rhs_code (stmt)) + { + case NEGATE_EXPR: + worklist.safe_push (gimple_assign_rhs1 (stmt)); + return true; + case MAX_EXPR: + [[fallthrough]]; + case MIN_EXPR: + [[fallthrough]]; + case POINTER_PLUS_EXPR: + [[fallthrough]]; + case POINTER_DIFF_EXPR: + [[fallthrough]]; + case PLUS_EXPR: + [[fallthrough]]; + case MINUS_EXPR: + worklist.safe_push (gimple_assign_rhs1 (stmt)); + worklist.safe_push (gimple_assign_rhs2 (stmt)); + return true; + case MULT_EXPR: + break; + default: + return false; + } + + /* For mult_expr, rhs2 must be an integer constant, so we can simply take + this constant as alignment. Otherwise, return false. */ + tree rhs2 = gimple_assign_rhs2 (stmt); + if (TREE_CODE (rhs2) != INTEGER_CST) + return false; + + alignment = new_alignment (alignment, abs_value (rhs2)); + return true; +} + +bool +alignment_propagator::check_param (tree t, auto_vec &worklist, + size_t &alignment) +{ + int index = get_param_index_from_ssa (t); + if (index == -1) + return false; + + for (cgraph_edge *e = node->callers; e; e = e->next_caller) + { + if (gimple_call_num_args (e->call_stmt) <= index) + return false; + + tree arg = gimple_call_arg (e->call_stmt, index); + if (e->caller == node) + worklist.safe_push (arg); + else + { + auto *align = alignment_map.get (SSA_NAME_VAR (t)); + if (!align) + return false; + + alignment = new_alignment (alignment, *align); + } + } + + return true; +} + +/* Find param from VAR and return its index. Return -1 if fail. */ + +int +alignment_propagator::get_param_index_from_ssa (tree var) +{ + if (!SSA_NAME_IS_DEFAULT_DEF (var) || !SSA_NAME_VAR (var)) + return -1; + + tree param = DECL_ARGUMENTS (cfun->decl); + int index = 0; + while (param && param != SSA_NAME_VAR (var)) + { + param = DECL_CHAIN (param); + index++; + } + + return index; +} + +/* Get alignment of an argument if it is calculated from the address of a + local variable. */ + +size_t +alignment_propagator::get_arg_alignment (cgraph_node *caller, tree arg) +{ + if (!caller || !arg) + return 0; + + cfun_saver save (caller); + + tree base = nullptr; + tree offset = nullptr; + + /* Extract base and offset. */ + if (TREE_CODE (arg) == ADDR_EXPR) + { + base = arg; + tree op0 = TREE_OPERAND (base, 0); + if (TREE_CODE (op0) == MEM_REF) + { + base = TREE_OPERAND (op0, 0); + offset = TREE_OPERAND (op0, 1); + } + } + else + { + if (TREE_CODE (arg) != SSA_NAME) + return 0; + + gimple *stmt = SSA_NAME_DEF_STMT (arg); + if (!is_gimple_assign (stmt)) + return 0; + + if (gimple_assign_rhs_code (stmt) == POINTER_PLUS_EXPR) + offset = gimple_assign_rhs2 (stmt); + else if (!gimple_assign_single_p (stmt)) + return 0; + + base = gimple_assign_rhs1 (stmt); + } + + /* Check if ARG uses the address of a local variable. */ + if (TREE_CODE (base) != ADDR_EXPR) + return 0; + + tree decl = TREE_OPERAND (base, 0); + if (!decl || !VAR_P (decl) + || decl_function_context (decl) != current_function_decl) + return 0; + + size_t alignment = LOCAL_DECL_ALIGNMENT (decl) / 8; + + /* Update alignment if there is an offset. */ + if (offset) + { + if (TREE_CODE (offset) != INTEGER_CST) + return 0; + + auto value = abs_value (offset); + if (!pow2_or_zerop (value)) + return 0; + + alignment = new_alignment (alignment, value); + } + + return alignment; +} + +size_t +alignment_propagator::new_alignment (size_t orig_value, size_t new_value) +{ + if (!new_value) + return orig_value; + + if (!orig_value || new_value < orig_value) + return new_value; + + return orig_value; +} + +bool +alignment_propagator::pow2_or_zerop (size_t value) +{ + return !(value & (value - 1)); +} + +size_t +alignment_propagator::abs_value (tree t) +{ + gcc_assert (TREE_CODE (t) == INTEGER_CST); + auto value = TREE_INT_CST_LOW (t); + + return std::abs (static_cast (value)); +} + +bool +alignment_propagator::candidate_stmt_p (gimple *stmt) +{ + if (!is_gimple_assign (stmt) + || gimple_assign_rhs_code (stmt) != BIT_AND_EXPR) + return false; + + tree var = gimple_assign_rhs1 (stmt); + tree rhs2 = gimple_assign_rhs2 (stmt); + + return rhs2 && TREE_CODE (rhs2) == INTEGER_CST + && TREE_INT_CST_LOW (rhs2) < get_var_alignment (var); +} + + +static unsigned +ipa_propagate_alignment (void) +{ + auto_vec candidate_nodes; + cgraph_node *cnode = NULL; + FOR_EACH_FUNCTION (cnode) + { + if (!cnode->real_symbol_p () || !cnode->definition + || !cnode->has_gimple_body_p () || cnode->inlined_to) + continue; + + cnode->get_body (); + candidate_nodes.safe_push (cnode); + } + + for (auto *node : candidate_nodes) + alignment_propagator (node).execute (); + + return 0; +} + +namespace { +const pass_data pass_data_ipa_alignment_propagation = { + SIMPLE_IPA_PASS, + "alignment-propagation", + OPTGROUP_NONE, + TV_IPA_ALIGNMENT_PROPAGATION, + (PROP_cfg | PROP_ssa), + 0, + 0, + (TODO_update_ssa), + (TODO_verify_all), +}; + +class pass_ipa_alignment_propagation + : public simple_ipa_opt_pass +{ +public: + pass_ipa_alignment_propagation (gcc::context *ctxt) + : simple_ipa_opt_pass (pass_data_ipa_alignment_propagation, ctxt) + {} + + virtual bool gate (function *) + { + return optimize >= 3 && flag_ipa_alignment_propagation; + } + + virtual unsigned execute (function *) + { + return ipa_propagate_alignment (); + } +}; +} /* namespace. */ + +simple_ipa_opt_pass * +make_pass_ipa_alignment_propagation (gcc::context *ctxt) +{ + return new pass_ipa_alignment_propagation (ctxt); +} diff --git a/gcc/ipa-array-dse.cc b/gcc/ipa-array-dse.cc new file mode 100644 index 00000000000..df973e849ba --- /dev/null +++ b/gcc/ipa-array-dse.cc @@ -0,0 +1,3317 @@ +/* Array dead store elimination + Copyright (C) 2021-2022 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#include "ipa-array-dse.h" + +#include "basic-block.h" +#include "bitmap.h" +#include "cgraph.h" +#include "cfghooks.h" +#include "cfgloop.h" +#include "cfg.h" +#include "fold-const.h" +#include "gimple.h" +#include "gimple-builder.h" +#include "gimple-iterator.h" +#include "gimple-pretty-print.h" +#include "gimple-ssa.h" +#include "gimple-walk.h" +#include "gimplify-me.h" +#include "ipa-utils.h" +#include "tree-phinodes.h" +#include "ssa-iterators.h" +#include "stringpool.h" +#include "tree-cfg.h" +#include "tree-dfa.h" +#include "tree-inline.h" +#include "tree-pass.h" +#include "tree-pretty-print.h" +#include "tree-ssanames.h" +#include "tree-vrp.h" +#include "tree.h" + +namespace array_dse { + +#define RANGE_TYPE long_long_integer_type_node +#define RANGE_INF LONG_LONG_MAX +#define RANGE_NINF LONG_LONG_MIN + +static inline bool +integer_cst_p (tree t) +{ + return TREE_CODE (t) == INTEGER_CST && !TREE_OVERFLOW (t); +} + +static tree +strip_base (tree addr) +{ + tree base = get_base_address (addr); + return TREE_CODE (base) == MEM_REF ? TREE_OPERAND (base, 0) : nullptr; +} + +static tree +strip_ssa_copy (tree var) +{ + if (!var || TREE_CODE (var) != SSA_NAME) + return var; + + while (true) + { + gimple *stmt = SSA_NAME_DEF_STMT (var); + if (!gimple_assign_single_p (stmt) && !gimple_assign_cast_p (stmt)) + break; + + tree rhs = gimple_assign_rhs1 (stmt); + if (!rhs || TREE_CODE (rhs) != SSA_NAME) + break; + + var = rhs; + } + + return var; +} + +static inline unsigned +greatest_common_divisor (unsigned a, unsigned b) +{ + return b == 0 ? a : greatest_common_divisor (b, a % b); +} + +static compare_result +opposite_compare_result (compare_result result) +{ + switch (result) + { + case COMPARE_ERROR: return COMPARE_ERROR; + case LT: return GT; + case EQ: return NE; + case GT: return LT; + case LE: return GE; + case GE: return LE; + case NE: return EQ; + } +} + +static tree_code +opposite_cond_code (tree_code code) +{ + switch (code) + { + case LT_EXPR: return GE_EXPR; + case LE_EXPR: return GT_EXPR; + case GT_EXPR: return LE_EXPR; + case GE_EXPR: return LT_EXPR; + case EQ_EXPR: return NE_EXPR; + case NE_EXPR: return EQ_EXPR; + default: + return ERROR_MARK; + } +} + +/* Calculate step of a loop variable, record all stmts that plus step. */ + +static int +calc_loop_var_step (tree loop_var, tree iterate_var, + hash_set *iterate_stmts = nullptr) +{ + int step = 0; + auto_bitmap visited; + auto_vec worklist; + worklist.safe_push (iterate_var); + + while (!worklist.is_empty ()) + { + tree t = worklist.pop (); + if (TREE_CODE (t) != SSA_NAME) + return 0; + + if (t == loop_var || !bitmap_set_bit (visited, SSA_NAME_VERSION (t))) + continue; + + gimple *stmt = SSA_NAME_DEF_STMT (t); + if (gimple_code (stmt) == GIMPLE_PHI) + { + for (unsigned i = 0; i < gimple_phi_num_args (stmt); i++) + worklist.safe_push (gimple_phi_arg_def (stmt, i)); + continue; + } + + /* Check iterate stmts' pattern: _2 = _1 + step. */ + if (!is_gimple_assign (stmt) + || (gimple_assign_rhs_code (stmt) != POINTER_PLUS_EXPR + && gimple_assign_rhs_code (stmt) != PLUS_EXPR)) + return 0; + + tree ptr = gimple_assign_rhs1 (stmt); + tree offset = gimple_assign_rhs2 (stmt); + if (TREE_CODE (offset) != INTEGER_CST) + return 0; + + worklist.safe_push (ptr); + HOST_WIDE_INT offset_val = TREE_INT_CST_LOW (offset); + if (step && offset_val != step) + return 0; + step = offset_val; + + if (iterate_stmts) + iterate_stmts->add (stmt); + } + + return step; +} + +/* VAR is a loop var when: + 1. VAR is defined by a phi in LOOP's header. + 2. The defination phi should have two args, one comes from preheader + and the other comes from latch. */ + +static bool +loop_var_p (loop_p loop, tree var) +{ + if (TREE_CODE (var) != SSA_NAME) + return false; + + gimple *stmt = SSA_NAME_DEF_STMT (var); + if (gimple_code (stmt) != GIMPLE_PHI || gimple_bb (stmt) != loop->header) + return false; + + edge preheader_edge = loop_preheader_edge (loop); + edge latch_edge = loop_latch_edge (loop); + + return preheader_edge && latch_edge + && PHI_ARG_DEF_FROM_EDGE (stmt, preheader_edge) + && PHI_ARG_DEF_FROM_EDGE (stmt, latch_edge); +} + +static inline tree +build_value (HOST_WIDE_INT value) +{ + return build_int_cst (RANGE_TYPE, value); +} + +static inline value_range +make_range (HOST_WIDE_INT value) +{ + tree v = build_value (value); + return value_range{v, v}; +} + +static inline value_range +make_range (HOST_WIDE_INT min, HOST_WIDE_INT max) +{ + return value_range{build_value (min), build_value (max)}; +} + +static infinite_kind +infinite_p (tree value) +{ + tree type = TREE_TYPE (value); + if (TREE_CODE (value) != INTEGER_CST || TYPE_PRECISION (type) == 1) + return infinite_kind::NON_INF; + + wide_int type_min = wi::min_value (TYPE_PRECISION (type), TYPE_SIGN (type)); + wide_int type_max = wi::max_value (TYPE_PRECISION (type), TYPE_SIGN (type)); + + if (INTEGRAL_TYPE_P (type) && !TYPE_UNSIGNED (type) + && wi::to_wide (value) == type_min) + return infinite_kind::NINF; + + if (wi::to_wide (value) == type_max) + return infinite_kind::INF; + + return infinite_kind::NON_INF; +} + +static inline HOST_WIDE_INT +get_multiplier (tree t) +{ + if (TREE_CODE (t) != MULT_EXPR + || TREE_CODE (TREE_OPERAND (t, 1)) != INTEGER_CST) + return 0; + + return TREE_INT_CST_LOW (TREE_OPERAND (t, 1)); +} + +static tree negate_tree (tree t); +static tree minus_tree (tree t1, tree t2); + +/* Convert negative multiplier to positive. */ + +static void +handle_negate_multiplier (tree t) +{ + HOST_WIDE_INT multiplier = get_multiplier (t); + if (multiplier >= 0) + return; + + tree lhs = TREE_OPERAND (t, 0); + if (TREE_CODE (lhs) == PLUS_EXPR) + { + TREE_OPERAND (t, 0) = minus_tree (negate_tree (TREE_OPERAND (lhs, 0)), + TREE_OPERAND (lhs, 1)); + TREE_OPERAND (t, 1) = build_int_cst (RANGE_TYPE, -multiplier); + } + else if (TREE_CODE (lhs) == MINUS_EXPR) + { + TREE_OPERAND (t, 0) = negate_tree (lhs); + TREE_OPERAND (t, 1) = build_int_cst (RANGE_TYPE, -multiplier); + } +} + +static tree +negate_tree (tree t) +{ + if (!t) + return nullptr; + + if (infinite_p (t) == infinite_kind::INF) + return build_value (RANGE_NINF); + else if (infinite_p (t) == infinite_kind::NINF) + return build_value (RANGE_INF); + else + return fold_build1 (NEGATE_EXPR, RANGE_TYPE, t); +} + +static tree +plus_tree (tree t1, tree t2) +{ + if (!t1 || !t2) + return nullptr; + + infinite_kind inf1 = infinite_p (t1); + infinite_kind inf2 = infinite_p (t2); + + if ((inf1 == infinite_kind::INF && inf2 == infinite_kind::NINF) + || (inf1 == infinite_kind::NINF && inf2 == infinite_kind::INF)) + return nullptr; + + if (inf1 == infinite_kind::NINF || inf2 == infinite_kind::NINF) + return build_value (RANGE_NINF); + + if (inf1 == infinite_kind::INF || inf2 == infinite_kind::INF) + return build_value (RANGE_INF); + + tree ret = fold_build2 (PLUS_EXPR, RANGE_TYPE, t1, t2); + handle_negate_multiplier (ret); + + return ret; +} + +static tree +minus_tree (tree t1, tree t2) +{ + if (!t1 || !t2) + return nullptr; + + infinite_kind inf1 = infinite_p (t1); + infinite_kind inf2 = infinite_p (t2); + + if ((inf1 == infinite_kind::INF && inf2 == infinite_kind::INF) + || (inf1 == infinite_kind::NINF && inf2 == infinite_kind::NINF)) + return nullptr; + + if (inf1 == infinite_kind::NINF || inf2 == infinite_kind::INF) + return build_value (RANGE_NINF); + + if (inf1 == infinite_kind::INF || inf2 == infinite_kind::NINF) + return build_value (RANGE_INF); + + tree ret = fold_build2 (MINUS_EXPR, RANGE_TYPE, t1, t2); + handle_negate_multiplier (ret); + + return ret; +} + +/* Callback for walk_tree, usage: + walk_tree (&A, sub_expr_p, B, nullptr) + + Check if B is sub expr of A. + */ + +static tree +sub_expr_p (tree *opnd_ptr, int *walk_subtrees ATTRIBUTE_UNUSED, void *data) +{ + tree opnd = *opnd_ptr; + tree var = static_cast (data); + + if (opnd == var) + return var; + + return NULL_TREE; +} + +static unsigned +get_ptr_layers (tree expr) +{ + unsigned layers = 0; + while (POINTER_TYPE_P (expr) || TREE_CODE (expr) == ARRAY_TYPE) + { + layers++; + expr = TREE_TYPE (expr); + } + + return layers; +} + +static bool +find_base (gimple *stmt ATTRIBUTE_UNUSED, tree base, + tree var ATTRIBUTE_UNUSED, void *data) +{ + return (TREE_CODE (base) == MEM_REF + && TREE_OPERAND (base, 0) == static_cast (data)); +} + +static bool +gimple_phi_arg_p (gimple *stmt, tree var) +{ + for (unsigned i = 0; i < gimple_phi_num_args (stmt); i++) + if (gimple_phi_arg_def (stmt, i) == var) + return true; + + return false; +} + +/* Returns the number of FIELD_DECLs in TYPE. */ + +static unsigned +fields_length (const_tree type) +{ + tree t = TYPE_FIELDS (type); + return list_length (t); +} + +/* Get unique base address of VAR. */ + +tree +addr_analyzer::get_address (tree var) +{ + if (tree *it = address_map.get (var)) + return *it; + + tree addr = analyze_address (var); + address_map.put (var, addr); + + return addr; +} + +/* Try to find the unique base address to which VAR is accessing in + current function. */ + +tree +addr_analyzer::analyze_address (tree var) +{ + tree addr = nullptr; + auto_bitmap visited; + auto_vec worklist; + worklist.safe_push (var); + + while (!worklist.is_empty ()) + { + tree t = worklist.pop (); + if (TREE_CODE (t) != SSA_NAME || !POINTER_TYPE_P (TREE_TYPE (t))) + return nullptr; + + if (!bitmap_set_bit (visited, SSA_NAME_VERSION (t))) + continue; + + if (SSA_NAME_IS_DEFAULT_DEF (t)) + { + tree new_addr = SSA_NAME_VAR (t); + if (!new_addr || (addr && addr != new_addr)) + return nullptr; + + addr = new_addr; + continue; + } + + gimple *def_stmt = SSA_NAME_DEF_STMT (t); + if (is_gimple_assign (def_stmt)) + { + if (!gimple_assign_single_p (def_stmt) + && !gimple_assign_cast_p (def_stmt) + && gimple_assign_rhs_code (def_stmt) != POINTER_PLUS_EXPR) + return nullptr; + + worklist.safe_push (gimple_assign_rhs1 (def_stmt)); + } + else if (gimple_code (def_stmt) == GIMPLE_PHI) + { + for (unsigned i = 0; i < gimple_phi_num_args (def_stmt); i++) + worklist.safe_push (gimple_phi_arg_def (def_stmt, i)); + } + else + return nullptr; + } + + return addr; +} + +array_dse_callee::array_dse_callee (cgraph_node *node) + : node (node) +{ +} + +/* Check if the node could be a candidate callee for array dse. */ + +bool +array_dse_callee::analyze () +{ + cfun_saver save (node, LOOPS_NORMAL | LOOPS_HAVE_RECORDED_EXITS); + + return filter_function () && find_candidate_array () + && find_length_param () && check_array_usage (); +} + +unsigned HOST_WIDE_INT +array_dse_callee::get_len_param_max () const +{ + return len_param_max; +} + +tree +array_dse_callee::mult_tree (basic_block bb, tree t1, tree t2) +{ + if (!bb || !t1 || !t2 || !integer_cst_p (t2) + || infinite_p (t2) != infinite_kind::NON_INF) + return nullptr; + + if (integer_zerop (t1) || integer_zerop (t2)) + return integer_zero_node; + + auto range1 = calc_tree_range (bb, t1); + if (tree_to_shwi (range1.min ()) < 0) + return nullptr; + + HOST_WIDE_INT multiplier = tree_to_shwi (t2); + if (infinite_p (t1) != infinite_kind::NON_INF) + return build_value (multiplier > 0 ? RANGE_INF : RANGE_NINF); + + return fold_build2 (MULT_EXPR, RANGE_TYPE, t1, t2); +} + +tree +array_dse_callee::div_tree (basic_block bb, tree t1, tree t2) +{ + if (!bb || !t1 || !t2 || !integer_cst_p (t2) + || infinite_p (t2) != infinite_kind::NON_INF) + return nullptr; + + if (integer_zerop (t2)) + return nullptr; + + if (integer_zerop (t1)) + return integer_zero_node; + + auto range1 = calc_tree_range (bb, t1); + if (tree_to_shwi (range1.min ()) < 0) + return nullptr; + + HOST_WIDE_INT divisor = tree_to_shwi (t2); + + if (infinite_p (t1) != infinite_kind::NON_INF) + return build_value (divisor > 0 ? RANGE_INF : RANGE_NINF); + + return fold_build2 (TRUNC_DIV_EXPR, RANGE_TYPE, t1, t2); +} + +tree +array_dse_callee::lshift_tree (tree t1, tree t2) +{ + if (!t1 || !t2 || !integer_cst_p (t2)) + return nullptr; + + if (infinite_p (t1) != infinite_kind::NON_INF) + return t1; + + return fold_build2 (LSHIFT_EXPR, RANGE_TYPE, t1, t2); +} + +tree +array_dse_callee::rshift_tree (tree t1, tree t2) +{ + if (!t1 || !t2 || !integer_cst_p (t2)) + return nullptr; + + if (infinite_p (t1) != infinite_kind::NON_INF) + return t1; + + return fold_build2 (RSHIFT_EXPR, RANGE_TYPE, t1, t2); +} + +tree +array_dse_callee::max_tree (basic_block bb, tree t1, tree t2) +{ + if (!bb || !t1 || !t2) + return nullptr; + + switch (compare_tree (bb, t1, t2)) + { + case EQ: return t1; + case GT: return t1; + case GE: return t1; + case LT: return t2; + case LE: return t2; + default: return nullptr; + } +} + +tree +array_dse_callee::min_tree (basic_block bb, tree t1, tree t2) +{ + if (!bb || !t1 || !t2) + return nullptr; + + switch (compare_tree (bb, t1, t2)) + { + case EQ: return t2; + case GT: return t2; + case GE: return t2; + case LT: return t1; + case LE: return t1; + default: return nullptr; + } +} + +/* Calculate the value of T, where T is an expression with len_main_var and + N_VALUE is len_main_var's value. */ + +HOST_WIDE_INT +array_dse_callee::calc_tree_value (tree t, HOST_WIDE_INT n_value) +{ + if (TREE_CODE (t) == INTEGER_CST) + return tree_to_shwi (t); + + if (t == len_main_var || t == signed_len_var) + return n_value; + + HOST_WIDE_INT op_value[2]; + for (int i = 0; i < std::min (2, tree_operand_length (t)); i++) + op_value[i] = calc_tree_value (TREE_OPERAND (t, i), n_value); + + switch (TREE_CODE (t)) + { + case NEGATE_EXPR: + return -op_value[0]; + case PLUS_EXPR: + return op_value[0] + op_value[1]; + case MINUS_EXPR: + return op_value[0] - op_value[1]; + case MULT_EXPR: + return op_value[0] * op_value[1]; + case TRUNC_DIV_EXPR: + return op_value[0] / op_value[1]; + case LSHIFT_EXPR: + return op_value[0] * (1 << op_value[1]); + case RSHIFT_EXPR: + return op_value[0] / (1 << op_value[1]); + default: + return 0; + } +} + +/* Calculate expression T's range. */ + +value_range +array_dse_callee::calc_tree_range (basic_block bb, tree t) +{ + if (!t) + return value_range{RANGE_TYPE}; + + if (TREE_CODE (t) == INTEGER_CST) + return make_range (tree_to_shwi (t)); + + if (t == len_main_var || t == signed_len_var) + return len_range_map.get_or_insert (bb); + + int len = tree_operand_length (t); + gcc_assert (len > 0); + value_range range1 = calc_tree_range (bb, TREE_OPERAND (t, 0)); + value_range range2; + if (len == 2) + range2 = calc_tree_range (bb, TREE_OPERAND (t, 1)); + + switch (TREE_CODE (t)) + { + /* Since the variable in both expressions is len_main_var and both + expressions are monotonically increasing, we can just substitute + the maximum and minimum values of len_main_var to calculate the + expression T's range. */ + case PLUS_EXPR: + case MINUS_EXPR: + { + tree op[2] = {TREE_OPERAND (t, 0), TREE_OPERAND (t, 1)}; + if (integer_cst_p (op[0]) || integer_cst_p (op[1])) + break; + + auto len_range = len_range_map.get_or_insert (bb); + auto len_min = tree_to_shwi (len_range.min ()); + auto len_max = tree_to_shwi (len_range.max ()); + auto min1 = calc_tree_value (op[0], len_min); + auto max1 = calc_tree_value (op[0], len_max); + auto min2 = calc_tree_value (op[1], len_min); + auto max2 = calc_tree_value (op[1], len_max); + + auto min = TREE_CODE (t) == PLUS_EXPR ? min1 + min2 : min1 - min2; + auto max = TREE_CODE (t) == PLUS_EXPR ? max1 + max2 : max1 - max2; + + if (min > max) + std::swap (min, max); + + return make_range (min, max); + } + default: + break; + } + + return build_range (bb, TREE_CODE (t), range1, range2); +} + +value_range +array_dse_callee::build_range (basic_block bb, tree_code op, + const value_range &r1, const value_range &r2) +{ + tree min = nullptr; + tree max = nullptr; + switch (op) + { + case NEGATE_EXPR: + min = negate_tree (r1.max ()); + max = negate_tree (r1.min ()); + break; + case PLUS_EXPR: + [[fallthrough]]; + case POINTER_PLUS_EXPR: + min = plus_tree (r1.min (), r2.min ()); + max = plus_tree (r1.max (), r2.max ()); + break; + case MINUS_EXPR: + [[fallthrough]]; + case POINTER_DIFF_EXPR: + min = minus_tree (r1.min(), r2.max ()); + max = minus_tree (r1.max(), r2.min ()); + break; + case MULT_EXPR: + min = mult_tree (bb, r1.min (), r2.min ()); + max = mult_tree (bb, r1.max (), r2.max ()); + break; + case TRUNC_DIV_EXPR: + min = div_tree (bb, r1.min (), r2.max ()); + max = div_tree (bb, r1.max (), r2.min ()); + break; + case LSHIFT_EXPR: + min = lshift_tree (r1.min (), r2.min ()); + max = lshift_tree (r1.max (), r2.max ()); + break; + case RSHIFT_EXPR: + min = rshift_tree (r1.min (), r2.max ()); + max = rshift_tree (r1.max (), r2.min ()); + break; + case MAX_EXPR: + min = max_tree (bb, r1.min (), r2.min ()); + max = max_tree (bb, r1.max (), r2.max ()); + break; + case MIN_EXPR: + min = min_tree (bb, r1.min (), r2.min ()); + max = min_tree (bb, r1.max (), r2.max ()); + break; + default: + break; + } + + return min && max ? value_range{min, max} : value_range{RANGE_TYPE}; +} + +/* Compare two pointer range value in BB. */ + +compare_result +array_dse_callee::compare_tree (basic_block bb, tree t1, tree t2) +{ + if (!bb || !t1 || !t2) + return COMPARE_ERROR; + + if (operand_equal_p (t1, t2)) + return EQ; + + auto ret = compare_tree_by_minus (bb, t1, t2); + if (!ret) + ret = opposite_compare_result (compare_tree_by_minus (bb, t2, t1)); + + return ret; +} + +compare_result +array_dse_callee::compare_tree_by_minus (basic_block bb, tree t1, tree t2) +{ + tree expr = minus_tree (t1, t2); + auto range = calc_tree_range (bb, expr); + HOST_WIDE_INT min = tree_to_shwi (range.min ()); + HOST_WIDE_INT max = tree_to_shwi (range.max ()); + if (min == 0) + return GE; + if (min > 0) + return GT; + if (max == 0) + return LE; + if (max < 0) + return LT; + + return COMPARE_ERROR; +} + +bool +array_dse_callee::filter_function () const +{ + return leaf_recursive_node_p (node) && no_return_p () + /* There must be two params: array and length. */ + && list_length (DECL_ARGUMENTS (node->decl)) == PARAM_NUM; +} + +/* Candidate callee must return no value. Each return block can't have any + stmt except a return stmt. */ + +bool +array_dse_callee::no_return_p () const +{ + tree return_type = TREE_TYPE (TREE_TYPE (cfun->decl)); + if (TREE_CODE (return_type) != VOID_TYPE) + return false; + + for (auto return_edge : EXIT_BLOCK_PTR_FOR_FN (cfun)->preds) + { + basic_block return_bb = return_edge->src; + if (!single_succ_p (return_bb)) + return false; + + gimple *stmt = first_stmt (return_bb); + if (gimple_code (stmt) != GIMPLE_RETURN + || gimple_return_retval (as_a (stmt))) + return false; + } + + return true; +} + +bool +array_dse_callee::find_main_vars () +{ + auto_bitmap visited; + tree default_def[PARAM_NUM] = {nullptr, nullptr}; + + /* Collect all params' default def. */ + unsigned i; + tree name; + FOR_EACH_SSA_NAME (i, name, cfun) + { + if (!SSA_NAME_IS_DEFAULT_DEF (name) + || SSA_NAME_IS_VIRTUAL_OPERAND (name)) + continue; + + gimple *stmt = SSA_NAME_DEF_STMT (name); + if (gimple_code (stmt) != GIMPLE_NOP) + return false; + + /* Each param should have an unique default ssa def. */ + int index = find_param_index (SSA_NAME_VAR (name)); + if (index == -1 || !bitmap_set_bit (visited, index)) + return false; + + default_def[index] = name; + } + + if (bitmap_count_bits (visited) != PARAM_NUM) + return false; + + array_main_var = default_def[array_param_index]; + len_main_var = default_def[len_param_index]; + + find_tail_recursive_loop (default_def); + + signed_len_var = fold_convert (RANGE_TYPE, len_main_var); + + return true; +} + +/* Try to find a tail recursive loop. */ + +void +array_dse_callee::find_tail_recursive_loop (tree *default_def) +{ + tree main_loop_var[PARAM_NUM] = {nullptr, nullptr}; + loop_p unique_loop = nullptr; + + for (unsigned i = 0; i < PARAM_NUM; i++) + { + tree name = default_def[i]; + + use_operand_p use_p; + gimple *stmt = nullptr; + if (!single_imm_use (name, &use_p, &stmt) + || gimple_code (stmt) != GIMPLE_PHI) + return; + + main_loop_var[i] = gimple_phi_result (stmt); + + /* Check if all main vars are defined in the same loop header. */ + basic_block bb = gimple_bb (stmt); + loop_p loop = bb->loop_father; + if (!loop || loop->num == 0 || !bb_loop_header_p (bb) + || (unique_loop && unique_loop != loop)) + return; + + unique_loop = loop; + } + + /* Multiple latch is not allow. */ + if (!unique_loop || !loop_latch_edge (unique_loop)) + return; + + /* The loop header must be the "first" block. There shouldn't be any + stmt before entering main loop. */ + basic_block entry_bb = ENTRY_BLOCK_PTR_FOR_FN (cfun); + basic_block preheader = loop_preheader_edge (unique_loop)->src; + if (single_succ_p (preheader) && single_pred_p (preheader) + && single_pred (preheader) == entry_bb && single_succ_p (entry_bb) + && empty_block_p (preheader)) + { + main_loop = unique_loop; + array_main_var = main_loop_var[array_param_index]; + len_main_var = main_loop_var[len_param_index]; + } +} + +/* Check if the function only store to the array passed by its param. */ + +bool +array_dse_callee::find_candidate_array () +{ + tree unique_array = nullptr; + basic_block bb; + FOR_EACH_BB_FN (bb, cfun) + { + for (auto gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + gimple *stmt = gsi_stmt (gsi); + if (gimple_clobber_p (stmt)) + continue; + + /* There are 3 kind of stmts may have store ops: GIMPLE_ASSIGN, + GIMPLE_CALL and GIMPLE_ASM. */ + if (gimple_has_volatile_ops (stmt) + || gimple_code (stmt) == GIMPLE_ASM) + return false; + + /* We have check that current function only has recursive call, + and it doesn't return a value, so we can skip call stmt. */ + if (gimple_code (stmt) != GIMPLE_ASSIGN) + continue; + + tree lhs = gimple_assign_lhs (stmt); + if (TREE_CODE (lhs) == SSA_NAME) + continue; + + tree base = strip_base (lhs); + if (!base || TREE_CODE (base) != SSA_NAME) + return false; + + tree array = analyzer.get_address (base); + if (!array || (unique_array && unique_array != array)) + return false; + + unique_array = array; + } + } + + if (!unique_array) + return false; + + int index = find_param_index (unique_array); + if (index < 0) + return false; + + array_param = unique_array; + array_param_index = index; + + if (dump_file) + { + fprintf (dump_file, "Found unique stored array: "); + print_generic_expr (dump_file, unique_array); + fprintf (dump_file, "\n"); + } + + return true; +} + +/* Check if the function has length param. */ + +bool +array_dse_callee::find_length_param () +{ + collect_read_write_ptrs (); + + tree len = nullptr; + unsigned size = 0; + for (auto ptr : all_ptrs) + if (!check_pointer (ptr, len, size)) + return false; + + if (!len || TREE_CODE (len) != SSA_NAME || !SSA_NAME_VAR (len)) + return false; + + int index = find_param_index (SSA_NAME_VAR (len)); + if (index < 0) + return false; + + len_param = SSA_NAME_VAR (len); + len_param_index = index; + elem_size = build_int_cst (RANGE_TYPE, size); + elem_size_cst = size; + calc_length_param_max (); + + if (len && dump_file) + { + fprintf (dump_file, "Found unique array length: "); + print_generic_expr (dump_file, len_param); + fprintf (dump_file, "\n"); + } + + return true; +} + +void +array_dse_callee::collect_read_write_ptrs () +{ + basic_block bb; + FOR_EACH_BB_FN (bb, cfun) + { + for (auto gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + gimple *stmt = gsi_stmt (gsi); + + for (unsigned i = 0; i < gimple_num_ops (stmt); i++) + { + tree op = gimple_op (stmt, i); + if (!op) + continue; + + tree base = strip_base (op); + if (!base || TREE_CODE (base) != SSA_NAME) + continue; + + tree array = analyzer.get_address (base); + if (array != array_param) + continue; + + all_ptrs.add (base); + } + } + } +} + +/* We heuristically set upper bound of length param to the max value with + half bits of its data type. + + TODO: Overflows may still occur, we need a better implement. + */ + +void +array_dse_callee::calc_length_param_max () +{ + unsigned bits = TYPE_PRECISION (TREE_TYPE (len_param)); + len_param_max = 1L << (bits / 2); +} + +/* Check pointer pattern: ptr = ptr1 + offset1 + | + ptr2 + offset2 + | + ... + | + ARRAY + offset3 + + All ptrs we visited must be calculated by adding offset to array_param. + All offset must be an expression with the only variable len_param. + LEN will be set to the unique variable we founded. + SIZE will be set to the minimum offset unit, which will be treated as the + array element size. + */ + +bool +array_dse_callee::check_pointer (tree ptr, tree &len, unsigned &size) +{ + visited_offset.empty (); + auto_bitmap visited; + auto_vec worklist; + worklist.safe_push (ptr); + + while (!worklist.is_empty ()) + { + tree t = worklist.pop (); + if (!POINTER_TYPE_P (TREE_TYPE (t)) || TREE_CODE (t) != SSA_NAME) + return false; + + if (!bitmap_set_bit (visited, SSA_NAME_VERSION (t))) + continue; + + if (SSA_NAME_IS_DEFAULT_DEF (t)) + { + tree var = SSA_NAME_VAR (t); + if (!var || var != array_param) + return false; + + continue; + } + + gimple *stmt = SSA_NAME_DEF_STMT (t); + if (is_gimple_assign (stmt)) + { + worklist.safe_push (gimple_assign_rhs1 (stmt)); + if (gimple_assign_rhs_code (stmt) == POINTER_PLUS_EXPR + && !check_offset (gimple_assign_rhs2 (stmt), len, size, + worklist)) + return false; + } + else if (gimple_code (stmt) == GIMPLE_PHI) + { + for (unsigned i = 0; i < gimple_phi_num_args (stmt); i++) + worklist.safe_push (gimple_phi_arg_def (stmt, i)); + } + else + return false; + } + + return true; +} + +/* Check offset part. */ + +bool +array_dse_callee::check_offset (tree var, tree &len, unsigned &size, + auto_vec &worklist) +{ + if (visited_offset.contains (var)) + return true; + visited_offset.add (var); + + if (TREE_CODE (TREE_TYPE (var)) != INTEGER_TYPE) + return false; + + tree offset = strip_ssa_copy (var); + if (TREE_CODE (offset) == INTEGER_CST) + { + HOST_WIDE_INT value = TREE_INT_CST_LOW (offset); + value = std::abs (value); + size = size ? greatest_common_divisor (size, value) : value; + return true; + } + + if (TREE_CODE (offset) != SSA_NAME) + return false; + + gimple *stmt = SSA_NAME_DEF_STMT (offset); + if (gimple_code (stmt) == GIMPLE_PHI) + { + for (unsigned i = 0; i < gimple_phi_num_args (stmt); i++) + if (!check_offset (gimple_phi_arg_def (stmt, i), len, size, worklist)) + return false; + } + else if (!is_gimple_assign (stmt)) + return false; + + switch (gimple_assign_rhs_code (stmt)) + { + case MAX_EXPR: + [[fallthrough]]; + case MIN_EXPR: + [[fallthrough]]; + case PLUS_EXPR: + [[fallthrough]]; + case MINUS_EXPR: + return check_offset (gimple_assign_rhs1 (stmt), len, size, worklist) + && check_offset (gimple_assign_rhs2 (stmt), len, size, + worklist); + case POINTER_DIFF_EXPR: + worklist.safe_push(gimple_assign_rhs1 (stmt)); + worklist.safe_push(gimple_assign_rhs2 (stmt)); + return true; + case NEGATE_EXPR: + return check_offset (gimple_assign_rhs1 (stmt), len, size, worklist); + case MULT_EXPR: + return check_mult_expr (stmt, len, size); + default: + return false; + } +} + +/* Handle MULT_EXPR. */ + +bool +array_dse_callee::check_mult_expr (gimple *stmt, tree &len, unsigned &size) +{ + tree rhs1 = gimple_assign_rhs1 (stmt); + tree rhs2 = gimple_assign_rhs2 (stmt); + + /* Handle size. */ + if (TREE_CODE (rhs2) != INTEGER_CST) + return false; + + HOST_WIDE_INT value = TREE_INT_CST_LOW (rhs2); + size = greatest_common_divisor (size, std::abs (value)); + + /* Handle index. */ + rhs1 = strip_ssa_copy (rhs1); + if (TREE_CODE (rhs1) != SSA_NAME) + return false; + + gimple *index_stmt = SSA_NAME_DEF_STMT (rhs1); + if (is_gimple_assign (index_stmt) && gimple_num_ops (index_stmt) > 2) + { + if (TREE_CODE (gimple_assign_rhs2 (index_stmt)) != INTEGER_CST) + return false; + rhs1 = gimple_assign_rhs1 (index_stmt); + } + + if (len && len != rhs1) + return false; + len = rhs1; + + return true; +} + +/* Find the param index of VAR in current function. + Return -1 if not found. */ + +int +array_dse_callee::find_param_index (tree var) +{ + if (TREE_CODE (var) != PARM_DECL) + return -1; + + tree param = DECL_ARGUMENTS (node->decl); + int index = 0; + while (param) + { + if (param == var) + return index; + + param = DECL_CHAIN (param); + index++; + } + + return -1; +} + +bool +array_dse_callee::check_array_usage () +{ + find_main_vars (); + + return calc_ptr_range () && check_ptr_range () + && check_recursive_call_arg (); +} + +/* Calculate len_param's value range in each block. + We assume its initial range is [1, len_param_max], we will validate this + range at each call to this callee. */ + +void +array_dse_callee::calc_len_range () +{ + /* Init all blocks' len_range. */ + auto full_len_range = make_range (len_param_min, len_param_max); + basic_block bb; + FOR_EACH_BB_FN (bb, cfun) + len_range_map.put (bb, full_len_range); + + /* Calculate new range according to condition. */ + FOR_EACH_BB_FN (bb, cfun) + { + gimple *stmt = gsi_stmt (gsi_last_bb (bb)); + auto cond_range = get_range_from_cond (stmt); + if (cond_range.undefined_p ()) + continue; + + edge true_edge = nullptr; + edge false_edge = nullptr; + extract_true_false_edges_from_block (bb, &true_edge, &false_edge); + update_len_range (true_edge->dest, cond_range); + update_len_range (false_edge->dest, invert_range (cond_range)); + } +} + +/* Update len_param's range in all block dominated by START. */ + +void +array_dse_callee::update_len_range (basic_block start, + const value_range &new_range) +{ + for (auto bb : get_all_dominated_blocks (CDI_DOMINATORS, start)) + (*len_range_map.get (bb)).intersect (new_range); +} + +value_range +array_dse_callee::invert_range (const value_range &range) const +{ + auto new_range = range; + new_range.invert (); + + return new_range; +} + +/* Get range of len_param from a condition. */ + +value_range +array_dse_callee::get_range_from_cond (gimple *stmt) +{ + if (!stmt || gimple_code (stmt) != GIMPLE_COND) + return value_range{}; + + gcond *cond = as_a (stmt); + tree_code code = gimple_cond_code (cond); + tree lhs = gimple_cond_lhs (cond); + tree rhs = gimple_cond_rhs (cond); + + if (lhs != len_main_var || TREE_CODE (rhs) != INTEGER_CST) + return value_range{}; + + HOST_WIDE_INT value = TREE_INT_CST_LOW (rhs); + + switch (code) + { + case LT_EXPR: + return make_range (RANGE_NINF, value - 1); + case LE_EXPR: + return make_range (RANGE_NINF, value); + case GT_EXPR: + return make_range (value + 1, RANGE_INF); + case GE_EXPR: + return make_range (value, RANGE_INF); + case EQ_EXPR: + return make_range (value); + case NE_EXPR: + return invert_range (make_range (value)); + default: + return value_range{}; + } +} + +/* Get range of a variable, represented by len_param. If variable is a + pointer, return the range of its offset from array_param. */ + +value_range +array_dse_callee::get_var_range (basic_block bb, tree var) +{ + if (var == array_main_var) + return make_range (0, 0); + + if (var == len_main_var) + return value_range{signed_len_var, signed_len_var}; + + if (find_var_range (var, bb)) + return var_range[var][bb]; + + /* If we can't calculate its range, keep it varying. */ + auto &range = var_range[var][bb]; + range.set_varying (RANGE_TYPE); + + if (TREE_CODE (var) == INTEGER_CST) + { + HOST_WIDE_INT value = TREE_INT_CST_LOW (var); + range = make_range (value); + return range; + } + + if (TREE_CODE (var) != SSA_NAME) + return range; + + /* Build range expression recursively. */ + gimple *stmt = SSA_NAME_DEF_STMT (var); + if (gimple_code (stmt) == GIMPLE_PHI) + { + range = get_var_range (bb, gimple_phi_arg_def (stmt, 0)); + for (unsigned i = 1; i < gimple_phi_num_args (stmt); i++) + { + tree arg = gimple_phi_arg_def (stmt, i); + auto arg_range = get_var_range (bb, arg); + tree min = min_tree (bb, range.min (), arg_range.min ()); + tree max = max_tree (bb, range.max (), arg_range.max ()); + if (!min || !max) + { + range.set_varying (RANGE_TYPE); + break; + } + + range = value_range{min, max}; + } + return range; + } + + if (!is_gimple_assign (stmt)) + return range; + + tree rhs1 = gimple_assign_rhs1 (stmt); + tree rhs2 = gimple_num_ops (stmt) > 2 ? gimple_assign_rhs2 (stmt) : nullptr; + value_range range1 = get_var_range (bb, rhs1); + value_range range2 = value_range{RANGE_TYPE}; + if (rhs2) + range2 = get_var_range (bb, rhs2); + + if (gimple_assign_single_p (stmt) || gimple_assign_cast_p (stmt)) + range = range1; + else + range = build_range (bb, gimple_assign_rhs_code (stmt), range1, range2); + + return range; +} + +/* Calculate pointer's offset range by checking loop condition. */ + +bool +array_dse_callee::calc_ptr_range () +{ + calc_len_range (); + + auto_bitmap visited; + auto_vec worklist; + loop_p l = main_loop ? main_loop : current_loops->tree_root; + worklist.safe_push (l->header); + + while (!worklist.is_empty ()) + { + basic_block bb = worklist.pop (); + if (bb == EXIT_BLOCK_PTR_FOR_FN (cfun) + || (main_loop && bb == main_loop->latch) + || unreachable_blocks.contains (bb)) + continue; + + if (bb->flags & BB_IRREDUCIBLE_LOOP) + return false; + + if (!bitmap_set_bit (visited, bb->index)) + continue; + + if (loop_header_p (bb) && !calc_loop_var_range (bb->loop_father)) + return false; + + for (auto succ : bb->succs) + worklist.safe_push (succ->dest); + } + + return true; +} + +/* Check if offset range of all pointers calculated by array_param are + within [0, (len_param -1) * elem_size]. */ + +bool +array_dse_callee::check_ptr_range () +{ + basic_block bb; + FOR_EACH_BB_FN (bb, cfun) + { + if (unreachable_blocks.contains (bb)) + continue; + + for (auto gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + gimple *stmt = gsi_stmt (gsi); + + for (unsigned i = 0; i < gimple_num_ops (stmt); i++) + { + tree op = gimple_op (stmt, i); + if (!op) + continue; + + tree base = strip_base (op); + if (!base || TREE_CODE (base) != SSA_NAME + || !all_ptrs.contains (base)) + continue; + + auto range = get_var_range (bb, base); + /* offset >= 0. */ + auto ret = compare_tree (bb, range.min (), integer_zero_node); + if (!ret || ret & LT) + return false; + + /* offset <= (n - 1) * elem_size. */ + tree tmp = minus_tree (signed_len_var, integer_one_node); + tmp = mult_tree (bb, tmp, elem_size); + ret = compare_tree (bb, range.max (), tmp); + if (!ret || ret & GT) + return false; + } + } + } + + return true; +} + +/* Check range of recursive call arguments: + void func(a, n) { + ... + func(a1, n1); + } + + 1. (1) a1 >= a (tail recursive call) + (2) a1 == a (normal recursive call) + 2. n1 >= 1 + 3. a1 + n1 * elem_size <= a + n * elem_size + */ + +bool +array_dse_callee::check_recursive_call_arg () +{ + auto_vec array_args; + auto_vec len_args; + auto_vec blocks; + auto_vec is_tail_recursive_call; + + collect_recursive_call_args (array_args, len_args, blocks, + is_tail_recursive_call); + + for (unsigned i = 0; i < array_args.length (); i++) + { + basic_block bb = blocks[i]; + update_branch_range (bb); + auto array_range = get_var_range (bb, array_args[i]); + auto len_range = get_var_range (bb, len_args[i]); + + /* Check requirement 2. */ + auto ret = compare_tree (bb, len_range.min (), integer_one_node); + if (!ret || ret & ~GE) + return false; + + if (is_tail_recursive_call[i]) + { + /* Check requirement 1.1. */ + ret = compare_tree (bb, array_range.min (), integer_zero_node); + if (!ret || ret & ~GE) + return false; + } + else + { + /* Check requirement 1.2. */ + if (!integer_zerop (array_range.min ()) + || !integer_zerop (array_range.max ())) + return false; + } + + /* Check requirement 3. */ + tree offset = build_recursive_offset (len_args[i]); + if (!offset) + return false; + + tree recursive_ptr_max + = build_recursive_ptr_range_max (bb, array_args[i], offset); + if (!recursive_ptr_max) + return false; + + tree upper_bound = mult_tree (bb, signed_len_var, elem_size); + ret = compare_tree (bb, recursive_ptr_max, upper_bound); + if (!ret || ret & ~LE) + return false; + } + + return true; +} + +void +array_dse_callee::collect_recursive_call_args ( + auto_vec &array_args, auto_vec &len_args, + auto_vec &blocks, auto_vec &is_tail_recursive_call) +{ + for (cgraph_edge *edge = node->callees; edge; edge = edge->next_callee) + { + if (node != edge->callee) + continue; + + gcall *call = edge->call_stmt; + tree array_arg = gimple_call_arg (call, array_param_index); + tree len_arg = gimple_call_arg (call, len_param_index); + + array_args.safe_push (array_arg); + len_args.safe_push (len_arg); + blocks.safe_push (gimple_bb (call)); + is_tail_recursive_call.safe_push (tail_recursive_call_p (call)); + } + + if (main_loop) + { + gimple *array_def_stmt = SSA_NAME_DEF_STMT (array_main_var); + gimple *len_def_stmt = SSA_NAME_DEF_STMT (len_main_var); + edge latch_edge = loop_latch_edge (main_loop); + tree array_arg = PHI_ARG_DEF_FROM_EDGE (array_def_stmt, latch_edge); + tree len_arg = PHI_ARG_DEF_FROM_EDGE (len_def_stmt, latch_edge); + array_args.safe_push (array_arg); + len_args.safe_push (len_arg); + blocks.safe_push (latch_edge->src); + is_tail_recursive_call.safe_push (true); + } +} + +/* If BB is first block after a condition jump, try to update range according + to the condition. */ + +void +array_dse_callee::update_branch_range (basic_block bb) +{ + if (!single_pred_p (bb)) + return; + + basic_block pred = single_pred (bb); + gimple *stmt = gsi_stmt (gsi_last_bb (pred)); + if (!stmt || gimple_code (stmt) != GIMPLE_COND) + return; + + tree lhs = gimple_cond_lhs (stmt); + tree rhs = gimple_cond_rhs (stmt); + if (!integer_cst_p (rhs)) + return; + + tree_code code = gimple_cond_code (stmt); + if (single_pred_edge (bb)->flags & EDGE_FALSE_VALUE) + code = opposite_cond_code (code); + + auto range = get_var_range (bb, lhs); + tree min = range.min (); + tree max = range.max (); + HOST_WIDE_INT value = TREE_INT_CST_LOW (rhs); + + switch (code) + { + case LT_EXPR: + value--; + [[fallthrough]]; + case LE_EXPR: + max = min_tree (bb, max, build_value (value)); + break; + case GT_EXPR: + value++; + [[fallthrough]]; + case GE_EXPR: + min = max_tree (bb, min, build_value (value)); + break; + case EQ_EXPR: + var_range[lhs][bb] = make_range (value); + return; + default: + return; + } + + var_range[lhs][bb] = value_range{min, max}; +} + +/* If LEN = (ptr1 - ptr2) / elem_size, + then recursive_offset = LEN * elem_size = (ptr1 - ptr2). + + We can do this only when ptr1 and ptr2 comes from array_param, + so (ptr1 - ptr2) is an integer multiple of elem_size. + */ + +tree +array_dse_callee::build_recursive_offset (tree len_arg) +{ + if (TREE_CODE (len_arg) != SSA_NAME) + return nullptr; + + gimple *stmt = SSA_NAME_DEF_STMT (len_arg); + if (!is_gimple_assign (stmt)) + return nullptr; + + /* Check pattern: (ptr1 - ptr2) / elem_size. */ + tree_code code = gimple_assign_rhs_code (stmt); + if (code != TRUNC_DIV_EXPR && code != RSHIFT_EXPR) + return nullptr; + + tree rhs1 = gimple_assign_rhs1 (stmt); + if (TREE_CODE (rhs1) != SSA_NAME) + return nullptr; + + gimple *def = SSA_NAME_DEF_STMT (strip_ssa_copy (rhs1)); + if (!is_gimple_assign (def) + || gimple_assign_rhs_code (def) != POINTER_DIFF_EXPR) + return nullptr; + + /* Check ptr1 and ptr2. */ + tree len = nullptr; + unsigned size = 0; + if (!check_pointer (gimple_assign_rhs1 (def), len, size) + || !check_pointer (gimple_assign_rhs2 (def), len, size) + || len != len_main_var || size != elem_size_cst) + return nullptr; + + tree rhs2 = gimple_assign_rhs2 (stmt); + if (!integer_cst_p (rhs2)) + return nullptr; + + HOST_WIDE_INT value = TREE_INT_CST_LOW (rhs2); + if (code == RSHIFT_EXPR) + value = 1 << value; + + if (value != elem_size_cst) + return nullptr; + + return rhs1; +} + +/* Build expression of recursive pointer range max. */ + +tree +array_dse_callee::build_recursive_ptr_range_max (basic_block bb, + tree array_arg, + tree offset) +{ + if (TREE_CODE (array_arg) != SSA_NAME) + return nullptr; + + tree recursive_ptr_max = nullptr; + gimple *stmt = SSA_NAME_DEF_STMT (array_arg); + + /* If ARRAY_ARG = rhs1 - offset, return rhs1's range max directly. */ + if (is_gimple_assign (stmt) + && gimple_assign_rhs_code (stmt) == POINTER_PLUS_EXPR) + { + tree rhs1 = gimple_assign_rhs1 (stmt); + tree rhs2 = gimple_assign_rhs2 (stmt); + if (TREE_CODE (rhs2) == SSA_NAME) + { + stmt = SSA_NAME_DEF_STMT (rhs2); + if (is_gimple_assign (stmt) + && gimple_assign_rhs_code (stmt) == NEGATE_EXPR + && gimple_assign_rhs1 (stmt) == offset) + recursive_ptr_max = get_var_range (bb, rhs1).max (); + } + } + + if (!recursive_ptr_max) + { + auto range1 = get_var_range (bb, array_arg); + auto range2 = get_var_range (bb, offset); + recursive_ptr_max = plus_tree (range1.max (), range2.max ()); + } + + return recursive_ptr_max; +} + +bool +array_dse_callee::tail_recursive_call_p (gimple *stmt) +{ + if (stmt->next) + return false; + + basic_block bb = gimple_bb (stmt); + return single_succ_p (bb) && return_bb_p (single_succ (bb)); +} + +bool +array_dse_callee::return_bb_p (basic_block bb) const +{ + return bb && single_succ_p (bb) && + single_succ (bb) == EXIT_BLOCK_PTR_FOR_FN (cfun); +} + +/* Calculate the range of a loop variable according to initial value and + loop exit condition. */ + +bool +array_dse_callee::calc_loop_var_range (loop_p loop) +{ + if (!loops_state_satisfies_p (LOOPS_HAVE_PREHEADERS) + && loops_state_satisfies_p (LOOPS_MAY_HAVE_MULTIPLE_LATCHES)) + return false; + + basic_block header = loop->header; + for (auto gsi = gsi_start_phis (header); !gsi_end_p (gsi); gsi_next (&gsi)) + { + gphi *phi = as_a (gsi_stmt (gsi)); + tree result = gimple_phi_result (phi); + if (!loop_var_p (loop, result)) + continue; + + tree iterate_var = PHI_ARG_DEF_FROM_EDGE (phi, loop_latch_edge (loop)); + int step = calc_loop_var_step (result, iterate_var); + unsigned abs_step = static_cast (std::abs (step)); + if (!step) + continue; + + if (POINTER_TYPE_P (TREE_TYPE (result))) + { + if (abs_step != elem_size_cst) + return false; + loop_ptrs[loop].add (result); + } + else if (TREE_CODE (TREE_TYPE (result)) != INTEGER_TYPE + || abs_step != 1) + return false; + + tree init_var = PHI_ARG_DEF_FROM_EDGE (phi, loop_preheader_edge (loop)); + auto init_range = get_var_range (header, init_var); + tree min = step > 0 ? init_range.min () : build_value (RANGE_NINF); + tree max = step > 0 ? build_value (RANGE_INF) : init_range.max (); + auto new_range = value_range{min, max}; + for (auto bb : get_all_dominated_blocks (CDI_DOMINATORS, header)) + { + if (!find_var_range (result, bb)) + { + var_range[result][bb] = new_range; + continue; + } + + auto &range = var_range[result][bb]; + min = max_tree (bb, min, range.min ()); + max = min_tree (bb, max, range.max ()); + if (!min || !max) + return false; + range = value_range {min, max}; + } + } + + if (!check_loop_exits (loop)) + return false; + + return true; +} + +bool +array_dse_callee::check_loop_exits (loop_p loop) +{ + for (auto edge : get_loop_exit_edges (loop)) + { + gimple *stmt = gsi_stmt (gsi_last_bb (edge->src)); + if (gimple_code (stmt) != GIMPLE_COND) + continue; + + gcond *cond = as_a (stmt); + tree lhs = gimple_cond_lhs (cond); + tree rhs = gimple_cond_rhs (cond); + + bool lhs_cand_p = loop_var_p (loop, lhs) || iterate_var_p (loop, lhs); + bool rhs_cand_p = loop_var_p (loop, rhs) || iterate_var_p (loop, rhs); + if (!lhs_cand_p && !rhs_cand_p) + continue; + + tree step = nullptr; + if (POINTER_TYPE_P (TREE_TYPE (lhs)) + && POINTER_TYPE_P (TREE_TYPE (rhs))) + { + if (TREE_CODE (lhs) != SSA_NAME || TREE_CODE (rhs) != SSA_NAME) + return false; + step = elem_size; + } + else if (TREE_CODE (TREE_TYPE (lhs)) == INTEGER_TYPE + && TREE_CODE (TREE_TYPE (rhs)) == INTEGER_TYPE) + { + if (!lhs_cand_p && !integer_cst_p (rhs)) + return false; + step = integer_one_node; + } + else + return false; + + tree_code code = gimple_cond_code (cond); + if (edge->flags & EDGE_TRUE_VALUE) + code = opposite_cond_code (code); + + if (!fill_loop_var_range (loop, code, lhs, rhs, step)) + return false; + + if (iterate_var_p (loop, lhs)) + lhs = get_loop_var (loop, lhs); + + if (!fill_loop_var_range (loop, code, lhs, rhs, step)) + return false; + } + + return true; +} + +/* fill loop variable range according to loop exit's condition and step. */ + +bool +array_dse_callee::fill_loop_var_range (loop_p loop, tree_code code, + tree lhs, tree rhs, tree step) +{ + for (auto bb : get_all_dominated_blocks (CDI_DOMINATORS, loop->header)) + { + auto lhs_range = get_var_range (bb, lhs); + auto rhs_range = get_var_range (bb, rhs); + tree lhs_min = lhs_range.min (); + tree lhs_max = lhs_range.max (); + tree rhs_min = rhs_range.min (); + tree rhs_max = rhs_range.max (); + bool in_loop = flow_bb_inside_loop_p (loop, bb); + + switch (code) + { + case LT_EXPR: + lhs_max = in_loop ? minus_tree (rhs_max, step) : rhs_max; + rhs_min = in_loop ? plus_tree (lhs_min, step) : lhs_min; + break; + case LE_EXPR: + lhs_max = in_loop ? rhs_max : plus_tree (rhs_max, step); + rhs_min = in_loop ? lhs_min : minus_tree (lhs_min, step); + break; + case GT_EXPR: + lhs_min = in_loop ? plus_tree (rhs_min, step) : rhs_min; + rhs_max = in_loop ? minus_tree (lhs_max, step) : lhs_max; + break; + case GE_EXPR: + lhs_min = in_loop ? rhs_min : minus_tree (rhs_min, step); + rhs_max = in_loop ? lhs_max : plus_tree (lhs_max, step); + break; + default: + return false; + } + + if (loop_var_p (loop, lhs) || iterate_var_p (loop, lhs)) + var_range[lhs][bb] = value_range{lhs_min, lhs_max}; + if (loop_var_p (loop, rhs) || iterate_var_p (loop, rhs)) + var_range[rhs][bb] = value_range{rhs_min, rhs_max}; + + if (integer_onep (step) && loop_var_p (loop, lhs) + && !fill_loop_ptr_range (loop, bb, minus_tree (lhs_max, lhs_min))) + return false; + } + + return true; +} + +/* If the variable in loop exit's condition is a integer, like + for (i = 0; i < n; i++) + + fill other pointers' range in the same loop. + */ + +bool +array_dse_callee::fill_loop_ptr_range (loop_p loop, basic_block bb, + tree loop_length) +{ + if (!loop_length) + return false; + + auto ret = compare_tree (bb, loop_length, integer_zero_node); + if (!ret) + return false; + + if (ret == LT) + { + unreachable_blocks.add (bb); + return true; + } + + tree length = mult_tree (bb, loop_length, elem_size); + if (!length) + return false; + + for (auto ptr : loop_ptrs[loop]) + { + auto &range = var_range[ptr][bb]; + tree min = range.min (); + tree max = range.max (); + if (infinite_p (min) != infinite_kind::NON_INF) + { + if (infinite_p (max) != infinite_kind::NON_INF) + return false; + min = minus_tree (max, length); + } + else if (infinite_p (max) != infinite_kind::NON_INF) + { + if (infinite_p (min) != infinite_kind::NON_INF) + return false; + max = plus_tree (min, length); + } + else + return false; + + range = value_range{min, max}; + } + + return true; +} + +bool +array_dse_callee::loop_header_p (basic_block bb) +{ + return bb_loop_header_p (bb) + && (!main_loop || bb->loop_father != main_loop); +} + +bool +array_dse_callee::iterate_var_p (loop_p loop, tree var) +{ + if (!var) + return false; + + tree loop_var = get_loop_var (loop, var); + return loop_var && loop_var != var; +} + +/* Find the loop variable from the GIVEN var throught its def chain. */ + +tree +array_dse_callee::get_loop_var (loop_p loop, tree var) +{ + if (TREE_CODE (var) != SSA_NAME || !SSA_NAME_VAR (var)) + return nullptr; + + tree result = nullptr; + + auto_bitmap visited; + auto_vec worklist; + worklist.safe_push (var); + + while (!worklist.is_empty ()) + { + tree t = worklist.pop (); + if (TREE_CODE (var) != SSA_NAME) + return nullptr; + + if (!bitmap_set_bit (visited, SSA_NAME_VERSION (t))) + continue; + + if (loop_var_p (loop, t) && SSA_NAME_VAR (t) == SSA_NAME_VAR (var)) + { + if (result && result != t) + return nullptr; + result = t; + continue; + } + + gimple *stmt = SSA_NAME_DEF_STMT (t); + basic_block bb = gimple_bb (stmt); + if (!bb || !flow_bb_inside_loop_p (loop, gimple_bb (stmt))) + return nullptr; + + if (gimple_code (stmt) == GIMPLE_PHI) + { + for (unsigned i = 0; i < gimple_phi_num_args (stmt); i++) + worklist.safe_push (gimple_phi_arg_def (stmt, i)); + continue; + } + + if (!is_gimple_assign (stmt) + || (gimple_assign_rhs_code (stmt) != POINTER_PLUS_EXPR + && gimple_assign_rhs_code (stmt) != PLUS_EXPR)) + return nullptr; + + worklist.safe_push (gimple_assign_rhs1 (stmt)); + } + + return result; +} + +bool +array_dse_callee::find_var_range (tree var, basic_block bb) +{ + auto iter1 = var_range.find (var); + if (iter1 == var_range.end ()) + return false; + + auto iter2 = iter1->second.find (bb); + return iter2 != iter1->second.end (); +} + +array_dse_edge::array_dse_edge (cgraph_edge *edge, array_dse_callee *callee) + : call_edge (edge), + callee (callee) +{ +} + +bool +array_dse_edge::analyze () +{ + cfun_saver save (call_edge->caller, LOOPS_NORMAL); + + if (gimple_call_num_args (call_edge->call_stmt) != callee->PARAM_NUM) + return false; + + return find_local_array_from_arg () && check_array_usage () + && check_len_arg_range (); +} + +bool +array_dse_edge::fully_redundant () +{ + return array_arg_start > read_upper_bound; +} + +tree +array_dse_edge::get_bound_addr () +{ + unsigned HOST_WIDE_INT bound_size = (read_upper_bound + 1) * elem_size; + tree bound_size_expr = build_int_cst (size_type_node, bound_size); + + tree addr_type = build_pointer_type (TREE_TYPE (array)); + tree array_addr = build1 (ADDR_EXPR, addr_type, array); + + return build2 (POINTER_PLUS_EXPR, addr_type, array_addr, bound_size_expr); +} + +/* Find the local array used by call argument. */ + +bool +array_dse_edge::find_local_array_from_arg () +{ + tree arg = gimple_call_arg (call_edge->call_stmt, callee->array_param_index); + + while (TREE_CODE (arg) == ADDR_EXPR || TREE_CODE (arg) == MEM_REF) + arg = TREE_OPERAND (arg, 0); + + if (!arg || !VAR_P (arg) || TREE_CODE (TREE_TYPE (arg)) != ARRAY_TYPE + || decl_function_context (arg) != current_function_decl) + return false; + + array = arg; + elem_size = tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (TREE_TYPE (array)))); + array_size = tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (array))) / elem_size; + + return true; +} + +bool +array_dse_edge::check_array_usage () +{ + if (!collect_array_accesses ()) + return false; + + if (!find_inner_array ()) + return false; + + for (auto [var, stmt] : array_accesses) + if (!check_access_kind (stmt, var)) + return false; + + collect_call_block_succs (); + if (!calc_read_bound () || !calc_array_arg_start ()) + return false; + + if (call_block_succs.contains (gimple_bb (call_edge->call_stmt)) + && !check_optimized_area_rewrite ()) + return false; + + return true; +} + +bool +array_dse_edge::collect_array_accesses () +{ + basic_block bb; + FOR_EACH_BB_FN (bb, cfun) + { + if (bb->flags & BB_IRREDUCIBLE_LOOP) + return false; + + for (auto gsi = gsi_start_phis (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + gphi *phi = as_a (gsi_stmt (gsi)); + tree result = gimple_phi_result (phi); + if (walk_tree (&result, sub_expr_p, array, nullptr)) + if (!check_array_access (phi, result)) + return false; + + for (unsigned i = 0; i < gimple_phi_num_args (phi); i++) + { + tree arg = gimple_phi_arg_def (phi, i); + if (!walk_tree (&arg, sub_expr_p, array, nullptr)) + continue; + + if (!check_array_access (phi, arg)) + return false; + } + } + + for (auto gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + gimple *stmt = gsi_stmt (gsi); + if (gimple_clobber_p (stmt) || call_stmt_p (stmt)) + continue; + + for (unsigned i = 0; i < gimple_num_ops (stmt); i++) + { + tree var = gimple_op (stmt, i); + if (!var) + continue; + + if (!walk_tree (&var, sub_expr_p, array, nullptr)) + continue; + + if (!is_gimple_assign (stmt)) + return false; + + if (!check_array_access (stmt, var)) + return false; + } + } + + } + + renumber_gimple_stmt_uids (cfun); + + return !array_accesses.is_empty (); +} + +bool +array_dse_edge::check_array_access (gimple *stmt, tree var) +{ + if (array_ref_p (var)) + return gimple_assign_single_p (stmt) && !array_accesses.put (var, stmt); + + if (array_addr_p (var)) + return check_array_address (stmt, var); + + return false; +} + +bool +array_dse_edge::check_array_address (gimple *stmt, tree addr) +{ + if (gimple_code (stmt) == GIMPLE_PHI) + return check_array_address (as_a (stmt), addr); + + if (is_gimple_assign (stmt)) + return check_array_address (as_a (stmt), addr); + + return false; +} + +bool +array_dse_edge::check_array_address (gphi *phi, tree addr) +{ + tree result = gimple_phi_result (phi); + if (TREE_CODE (result) != SSA_NAME) + return false; + + if (array_address_vars.contains (result)) + return true; + + for (unsigned i = 0; i < gimple_phi_num_args (phi); i++) + { + tree arg = gimple_phi_arg_def (phi, i); + if (arg == addr) + continue; + + if (TREE_CODE (arg) != SSA_NAME) + return false; + + /* Only support simple loop variable: VAR is the initial address of + phi RESULT and other ARG must be defined by RESULT + offset. */ + gimple *stmt = SSA_NAME_DEF_STMT (arg); + if (!is_gimple_assign (stmt) + || gimple_assign_rhs_code (stmt) != POINTER_PLUS_EXPR + || gimple_assign_rhs1 (stmt) != result) + return false; + } + + array_address_vars.add (result); + + return check_access_from_address (result); +} + +bool +array_dse_edge::check_array_address (gassign *assign, tree addr) +{ + if (!gimple_assign_single_p (assign) + && gimple_assign_rhs_code (assign) != POINTER_PLUS_EXPR + && gimple_assign_rhs1 (assign) != addr) + return false; + + tree lhs = gimple_assign_lhs (assign); + if (TREE_CODE (lhs) != SSA_NAME) + return false; + + array_address_vars.add (lhs); + + return check_access_from_address (lhs); +} + +bool +array_dse_edge::check_access_from_address (tree addr) +{ + gimple *stmt; + imm_use_iterator iter; + FOR_EACH_IMM_USE_STMT (stmt, iter, addr) + { + for (unsigned i = 0; i < gimple_num_ops (stmt); i++) + { + tree op = gimple_op (stmt, i); + if (walk_tree (&op, sub_expr_p, addr, nullptr) + && !check_array_access (stmt, op)) + return false; + } + } + + return true; +} + +bool +array_dse_edge::check_access_kind (gimple *stmt, tree var) +{ + gcc_assert (gimple_assign_single_p (stmt)); + + auto &kind = access_kinds.get_or_insert (var); + + tree lhs = gimple_assign_lhs (stmt); + if (var == lhs) + { + kind = WRITE; + return true; + } + + gcc_assert (var == gimple_assign_rhs1 (stmt)); + + if (!inner_array) + { + kind = READ; + return true; + } + + auto_bitmap visited; + kind = check_access_kind_iterate (lhs, visited); + return kind != ACCESS_ERROR; +} + +access_kind +array_dse_edge::check_access_kind_iterate (tree var, auto_bitmap &visited) +{ + if (!var || TREE_CODE (var) != SSA_NAME) + return ACCESS_ERROR; + + if (!bitmap_set_bit (visited, SSA_NAME_VERSION (var))) + return NONE; + + int kind = NONE; + + imm_use_iterator iter; + gimple *stmt = nullptr; + FOR_EACH_IMM_USE_STMT (stmt, iter, var) + { + if (walk_stmt_load_store_ops (stmt, var, find_base, nullptr)) + kind |= READ; + + if (walk_stmt_load_store_ops (stmt, var, nullptr, find_base)) + kind |= WRITE; + + if (kind) + continue; + + tree next_var = nullptr; + if (is_gimple_assign (stmt)) + { + if ((!gimple_assign_single_p (stmt) && !gimple_assign_cast_p (stmt)) + || gimple_assign_rhs1 (stmt) != var) + return ACCESS_ERROR; + + tree lhs = gimple_assign_lhs (stmt); + if (array_ref_p (lhs)) + { + kind |= READ; + continue; + } + + next_var = lhs; + } + else if (gimple_code (stmt) == GIMPLE_PHI) + { + if (gimple_phi_arg_p (stmt, var)) + next_var = gimple_phi_result (stmt); + } + else if (gimple_code (stmt) == GIMPLE_COND) + { + if (gimple_cond_lhs (stmt) == var || gimple_cond_rhs (stmt) == var) + { + kind |= READ; + continue; + } + } + + access_kind next_kind = check_access_kind_iterate (next_var, visited); + if (next_kind == ACCESS_ERROR) + return ACCESS_ERROR; + + kind |= next_kind; + } + + return static_cast (kind); +} + +bool +array_dse_edge::find_inner_array () +{ + tree type = TREE_TYPE (array); + unsigned ptr_layers = get_ptr_layers (type); + gcc_assert (ptr_layers); + + /* No inner source array. */ + if (ptr_layers == 1) + { + inner_elem_type = TREE_TYPE (array); + return true; + } + + /* It's hard to trace all source of array. */ + if (ptr_layers > 2 + || TREE_CODE (TREE_TYPE (TREE_TYPE (type))) != RECORD_TYPE) + return false; + + inner_elem_type = TREE_TYPE (TREE_TYPE (type)); + + for (auto [var, stmt] : array_accesses) + { + tree lhs = gimple_get_lhs (stmt); + if (lhs != var) + continue; + + if (!array_ref_p (lhs) || !is_gimple_assign (stmt)) + return false; + + tree rhs = gimple_assign_rhs1 (stmt); + if (array_ref_p (rhs)) + continue; + + if (TREE_CODE (rhs) != SSA_NAME) + return false; + + tree base = rhs; + gimple *def_stmt = SSA_NAME_DEF_STMT (base); + while (is_gimple_assign (def_stmt) + && gimple_assign_rhs_code (def_stmt) == POINTER_PLUS_EXPR) + { + base = gimple_assign_rhs1 (def_stmt); + if (TREE_CODE (base) != SSA_NAME) + return false; + def_stmt = SSA_NAME_DEF_STMT (base); + } + + if (!gimple_call_builtin_p (def_stmt, BUILT_IN_CALLOC)) + return false; + + /* Only support unique source. The inner_array must be used only once, + assigned its address to the candidate array. */ + if (inner_array) + return false; + + /* array: T *[], base: T *. */ + if (TREE_TYPE (TREE_TYPE (array)) != TREE_TYPE (base)) + return false; + + if (!unique_use_p (base, stmt) || !initialize_assign_p (stmt)) + return false; + + inner_array = base; + } + + return true; +} + +bool +array_dse_edge::unique_use_p (tree var, gimple *unique_assign) const +{ + auto_vec worklist; + auto_bitmap visited; + worklist.safe_push (var); + + while (!worklist.is_empty ()) + { + tree t = worklist.pop (); + if (TREE_CODE (t) != SSA_NAME) + return false; + + if (!bitmap_set_bit (visited, SSA_NAME_VERSION (t))) + continue; + + imm_use_iterator iter; + gimple *stmt = nullptr; + FOR_EACH_IMM_USE_STMT (stmt, iter, t) + { + if (gimple_call_builtin_p (stmt, BUILT_IN_FREE)) + continue; + + if (!is_gimple_assign (stmt)) + return false; + + if (stmt == unique_assign) + continue; + + worklist.safe_push (gimple_assign_lhs (stmt)); + } + } + + return true; +} + +bool +array_dse_edge::initialize_assign_p (gimple *stmt) const +{ + if (!stmt || !gimple_bb (stmt)) + return false; + + hash_set preds; + auto_vec worklist; + worklist.safe_push (gimple_bb (stmt)); + + while (!worklist.is_empty ()) + { + basic_block bb = worklist.pop (); + if (preds.add (bb)) + continue; + + for (auto e : bb->preds) + worklist.safe_push (e->src); + } + + for (auto [var, access_stmt] : array_accesses) + { + if (access_stmt == stmt) + continue; + + if (preds.contains (gimple_bb (access_stmt))) + return false; + } + + return true; +} + +bool +array_dse_edge::calc_read_bound () +{ + for (auto [var, stmt] : array_accesses) + { + if (!after_call_stmt_p (stmt) || !read_array_p (var)) + continue; + + auto range = calc_ref_range (var); + if (!integer_cst_p (range.max ())) + return false; + + auto max = tree_to_shwi (range.max ()); + if (max % elem_size) + return false; + + if (max / elem_size > read_upper_bound) + read_upper_bound = max / elem_size; + } + + return true; +} + +value_range +array_dse_edge::calc_ref_range (tree var) +{ + tree_code code = TREE_CODE (var); + /* Array_ref's second op is an index. Convert it to address offset. */ + if (code == ARRAY_REF) + { + auto r = calc_offset_range (TREE_OPERAND (var, 1)); + if (r.varying_p ()) + return r; + + gcc_assert (integer_cst_p (r.min ()) && integer_cst_p (r.max ())); + return make_range (tree_to_shwi (r.min ()) * elem_size, + tree_to_shwi (r.max ()) * elem_size); + } + + gcc_assert (code == MEM_REF); + auto r1 = calc_addr_range (TREE_OPERAND (var, 0)); + auto r2 = calc_offset_range (TREE_OPERAND (var, 1)); + + return value_range{plus_tree (r1.min (), r2.min ()), + plus_tree (r1.max (), r2.max ())}; +} + +value_range +array_dse_edge::calc_addr_range (tree var) +{ + if (array_address_vars.contains (var)) + { + gcc_assert (TREE_CODE (var) == SSA_NAME); + gimple *stmt = SSA_NAME_DEF_STMT (var); + if (is_gimple_assign (stmt)) + { + auto r1 = calc_addr_range (gimple_assign_rhs1 (stmt)); + auto r2 = calc_offset_range (gimple_assign_rhs2 (stmt)); + return value_range{plus_tree (r1.min (), r2.min ()), + plus_tree (r1.max (), r2.max ())}; + } + + return calc_simple_loop_range (var); + } + + if (TREE_CODE (var) != ADDR_EXPR) + return value_range{RANGE_TYPE}; + + tree op = TREE_OPERAND (var, 0); + if (op == array) + return make_range (0); + + if (!array_ref_p (op)) + return value_range{RANGE_TYPE}; + + return calc_ref_range (op); +} + +value_range +array_dse_edge::calc_offset_range (tree offset) +{ + tree var = strip_ssa_copy (offset); + if (integer_cst_p (var)) + return make_range (tree_to_shwi (var)); + + if (TREE_CODE (var) != SSA_NAME) + return value_range{RANGE_TYPE}; + + gimple *stmt = SSA_NAME_DEF_STMT (var); + if (gimple_code (stmt) == GIMPLE_PHI) + return calc_simple_loop_range (var); + + if (!is_gimple_assign (stmt) || gimple_assign_rhs_code (stmt) != MULT_EXPR + || !integer_cst_p (gimple_assign_rhs2 (stmt)) + || TREE_INT_CST_LOW (gimple_assign_rhs2 (stmt)) != elem_size) + return value_range{RANGE_TYPE}; + + auto range = calc_offset_range (gimple_assign_rhs1 (stmt)); + if (!integer_cst_p (range.min ()) || !integer_cst_p (range.max ())) + return value_range{RANGE_TYPE}; + + return make_range (tree_to_shwi (range.min ()) * elem_size, + tree_to_shwi (range.max ()) * elem_size); +} + +value_range +array_dse_edge::calc_simple_loop_range (tree var) +{ + gimple *stmt = SSA_NAME_DEF_STMT (var); + basic_block bb = gimple_bb (stmt); + loop_p loop = bb->loop_father; + + if (!loop || loop->header != bb || !loop->any_upper_bound) + return value_range{RANGE_TYPE}; + + tree init_var = PHI_ARG_DEF_FROM_EDGE (stmt, loop_preheader_edge (loop)); + tree iterate_var = PHI_ARG_DEF_FROM_EDGE (stmt, loop_latch_edge (loop)); + + value_range init_range; + if (array_addr_p (init_var)) + init_range = calc_addr_range (init_var); + else + init_range = calc_offset_range (init_var); + + if (!init_range.singleton_p () || !integer_cst_p (init_range.min ())) + return value_range{RANGE_TYPE}; + + HOST_WIDE_INT init_value = tree_to_shwi (init_range.min ()); + int step = calc_loop_var_step (var, iterate_var); + int upper_bound = loop->nb_iterations_upper_bound.to_shwi (); + + return make_range (init_value, init_value + step * upper_bound); +} + +void +array_dse_edge::collect_call_block_succs () +{ + basic_block call_block = gimple_bb (call_edge->call_stmt); + auto_vec worklist; + for (auto e : call_block->succs) + worklist.safe_push (e->dest); + + while (!worklist.is_empty ()) + { + basic_block bb = worklist.pop (); + if (call_block_succs.add (bb)) + continue; + + for (auto e : bb->succs) + worklist.safe_push (e->dest); + } +} + +bool +array_dse_edge::calc_array_arg_start () +{ + tree array_arg = gimple_call_arg (call_edge->call_stmt, + callee->array_param_index); + if (!array_addr_p (array_arg)) + return false; + + auto range = calc_addr_range (array_arg); + if (!range.singleton_p () || !integer_cst_p (range.min ())) + return false; + + auto value = tree_to_shwi (range.min ()); + if (value % elem_size) + return false; + + array_arg_start = value / elem_size; + return true; +} + +bool +array_dse_edge::check_optimized_area_rewrite () +{ + tree arg = gimple_call_arg (call_edge->call_stmt, callee->len_param_index); + if (!arg) + return false; + + tree var = strip_ssa_copy (arg); + if (!var || TREE_CODE (var) != SSA_NAME) + return false; + + gimple *stmt = SSA_NAME_DEF_STMT (var); + loop_p loop = gimple_bb (stmt)->loop_father; + if (!loop || !loop_var_p (loop, var)) + return false; + + /* To make sure the optimized area of array is fully rewritten, the loop + step must be 1. We only support one iterate stmt now. */ + tree iterate_var = PHI_ARG_DEF_FROM_EDGE (stmt, loop_latch_edge (loop)); + hash_set iterate_stmts; + if (calc_loop_var_step (var, iterate_var, &iterate_stmts) != 1 + || iterate_stmts.elements () != 1) + return false; + + gimple *iter_stmt = *iterate_stmts.begin (); + if (gimple_assign_rhs1 (iter_stmt) != var) + return false; + + /* Check if the element has been fully written. */ + basic_block bb = gimple_bb (iter_stmt); + if (!check_full_write_elem (bb, gimple_assign_lhs (iter_stmt))) + return false; + + /* Check if the start address is less equal than the read_upper_bound. */ + tree init_var = PHI_ARG_DEF_FROM_EDGE (stmt, loop_preheader_edge (loop)); + init_var = strip_init_var (init_var, var); + if (!init_var) + return false; + + auto range = calc_offset_range (init_var); + if (!integer_cst_p (range.min ()) || !integer_cst_p (range.max ())) + return false; + + len_arg_min = tree_to_shwi (range.min ()); + + return tree_to_shwi (range.max ()) + array_arg_start <= read_upper_bound; +} + +bool +array_dse_edge::check_full_write_elem (basic_block bb, tree index) +{ + hash_set visited_fields; + + for (auto [var, stmt] : array_accesses) + { + /* Must in the same block. */ + if (gimple_bb (stmt) != bb) + continue; + + if (!write_array_p (var)) + continue; + + if (TREE_CODE (var) != MEM_REF + || !array_index_of_addr_p (index, TREE_OPERAND (var, 0)) + || !integer_zerop (TREE_OPERAND (var, 1))) + continue; + + /* Directly write to array. */ + tree lhs = gimple_assign_lhs (stmt); + if (var == lhs) + return true; + else if (!inner_array) + continue; + + imm_use_iterator iter; + gimple *use_stmt = nullptr; + FOR_EACH_IMM_USE_STMT (use_stmt, iter, lhs) + { + tree ref = gimple_get_lhs (use_stmt); + if (!ref || TREE_CODE (ref) != COMPONENT_REF) + continue; + + visited_fields.add (TREE_OPERAND (ref, 1)); + } + } + + return inner_array + && visited_fields.elements () == fields_length (inner_elem_type); +} + +bool +array_dse_edge::array_index_of_addr_p (tree index, tree addr) +{ + if (TREE_CODE (index) != SSA_NAME || TREE_CODE (addr) != SSA_NAME) + return false; + + /* Check pattern: addr = &array + offset. */ + gimple *stmt = SSA_NAME_DEF_STMT (addr); + if (!is_gimple_assign (stmt) + || gimple_assign_rhs_code (stmt) != POINTER_PLUS_EXPR) + return false; + + tree rhs1 = gimple_assign_rhs1 (stmt); + if (TREE_CODE (rhs1) != ADDR_EXPR || TREE_OPERAND (rhs1, 0) != array) + return false; + + tree rhs2 = gimple_assign_rhs2 (stmt); + if (TREE_CODE (rhs2) != SSA_NAME) + return false; + + /* Check pattern: offset = index * elem_size. */ + stmt = SSA_NAME_DEF_STMT (rhs2); + if (!is_gimple_assign (stmt) || gimple_assign_rhs_code (stmt) != MULT_EXPR) + return false; + + rhs1 = gimple_assign_rhs1 (stmt); + rhs2 = gimple_assign_rhs2 (stmt); + + return strip_ssa_copy (rhs1) == index && integer_cst_p (rhs2) + && TREE_INT_CST_LOW (rhs2) == elem_size; +} + +tree +array_dse_edge::strip_init_var (tree init_var, tree var) +{ + tree last = var; + while (true) + { + if (TREE_CODE (init_var) != SSA_NAME) + break; + + gimple *stmt = SSA_NAME_DEF_STMT (init_var); + loop_p loop = gimple_bb (stmt)->loop_father; + if (!loop || !loop_var_p (loop, init_var)) + break; + + auto latch_edge = loop_latch_edge (loop); + auto preheader_edge = loop_preheader_edge (loop); + if (!latch_edge || !preheader_edge + || PHI_ARG_DEF_FROM_EDGE (stmt, latch_edge) != last) + break; + + last = init_var; + init_var = PHI_ARG_DEF_FROM_EDGE (stmt, preheader_edge); + } + + return strip_ssa_copy (init_var); +} + +bool +array_dse_edge::check_len_arg_range () +{ + return check_len_arg_lower_bound () + && check_len_arg_upper_bound (); +} + +/* Check: ARG >= 1 (assumption in callee analysis). */ + +bool +array_dse_edge::check_len_arg_lower_bound () +{ + if (len_arg_min >= 1) + return true; + + /* If the len_arg_min recorded previous doesn't meet the condition, try to + update it by checking condition jump. */ + tree arg = gimple_call_arg (call_edge->call_stmt, callee->len_param_index); + if (!arg) + return false; + + tree var = strip_ssa_copy (arg); + if (!var || TREE_CODE (var) != SSA_NAME) + return false; + + basic_block call_block = gimple_bb (call_edge->call_stmt); + basic_block bb; + FOR_EACH_BB_FN (bb, cfun) + { + gimple *stmt = last_stmt (bb); + if (!stmt || gimple_code (stmt) != GIMPLE_COND) + continue; + + tree_code code = gimple_cond_code (stmt); + tree lhs = gimple_cond_lhs (stmt); + tree rhs = gimple_cond_rhs (stmt); + + if (lhs != var || !integer_cst_p (rhs)) + continue; + + edge true_edge; + edge false_edge; + extract_true_false_edges_from_block (bb, &true_edge, &false_edge); + if (!true_edge || !false_edge) + continue; + + if (dominated_by_p (CDI_DOMINATORS, call_block, false_edge->dest)) + code = opposite_cond_code (code); + else if (!dominated_by_p (CDI_DOMINATORS, call_block, true_edge->dest)) + continue; + + HOST_WIDE_INT rvalue = TREE_INT_CST_LOW (rhs); + switch (code) + { + case GT_EXPR: + len_arg_min = std::max (len_arg_min, rvalue + 1); + break; + case GE_EXPR: + [[fallthrough]]; + case EQ_EXPR: + len_arg_min = std::max (len_arg_min, rvalue); + break; + case NE_EXPR: + if (len_arg_min == rvalue) + len_arg_min++; + break; + default: + break; + } + } + + return len_arg_min >= 1; +} + +/* We can assume that the array will not be accessed out of bounds. + So we use array_size as the upper bound of len arg. */ + +bool +array_dse_edge::check_len_arg_upper_bound () +{ + return array_size <= callee->get_len_param_max (); +} + +bool +array_dse_edge::after_call_stmt_p (gimple *stmt) +{ + if (call_stmt_p (stmt)) + return false; + + basic_block bb = gimple_bb (stmt); + if (bb == gimple_bb (call_edge->call_stmt) + && gimple_uid (stmt) > gimple_uid (call_edge->call_stmt)) + return true; + + return call_block_succs.contains (bb); +} + +bool +array_dse_edge::write_array_p (tree var) +{ + auto *kind = access_kinds.get (var); + if (!kind) + return false; + + return *kind & WRITE; +} + +bool +array_dse_edge::read_array_p (tree var) +{ + auto *kind = access_kinds.get (var); + if (!kind) + return false; + + return *kind & READ; +} + +bool +array_dse_edge::call_stmt_p (gimple *stmt) const +{ + return stmt == call_edge->call_stmt; +} + +bool +array_dse_edge::array_ref_p (tree var) +{ + if (!var) + return false; + + if (TREE_CODE (var) == ARRAY_REF) + return TREE_OPERAND (var, 0) == array; + + return (TREE_CODE (var) == MEM_REF && array_addr_p (TREE_OPERAND (var, 0))); +} + +bool +array_dse_edge::array_addr_p (tree var) +{ + if (array_address_vars.contains (var)) + return true; + + if (TREE_CODE (var) != ADDR_EXPR) + return false; + + tree op = TREE_OPERAND (var, 0); + return op == array || array_ref_p (op); +} + +unsigned +ipa_array_dse::execute () +{ + cgraph_node *node; + FOR_EACH_FUNCTION (node) + { + if (!node->real_symbol_p () || !node->definition + || !node->has_gimple_body_p () || node->inlined_to) + continue; + node->get_body (); + + if (!DECL_STRUCT_FUNCTION (node->decl)) + continue; + + nodes.safe_push (node); + } + + if (!find_array_dse_candidate_callees ()) + { + if (dump_file) + fprintf (dump_file, "Fail finding array dse candidate callees\n"); + return 0; + } + + if (!find_array_dse_candidate_edges ()) + { + if (dump_file) + fprintf (dump_file, "Fail finding array dse candidate edges\n"); + return 0; + } + + for (auto edge : candidate_edges) + apply_array_dse (edge); + + symtab->remove_unreachable_nodes (dump_file); + + return TODO_update_ssa; +} + +bool +ipa_array_dse::find_array_dse_candidate_callees () +{ + if (dump_file) + fprintf (dump_file, "Finding array dse candidate callees\n\n"); + + for (auto node : nodes) + { + if (!tree_versionable_function_p (node->decl) + || !opt_for_fn (node->decl, optimize)) + continue; + + const char *fn_name = node->dump_asm_name (); + if (dump_file) + fprintf (dump_file, "Analyzing callee: %s\n", fn_name); + + auto *callee = new array_dse_callee (node); + if (!callee->analyze ()) + { + delete callee; + continue; + } + + if (dump_file) + { + fprintf (dump_file, "Found candidate callee: %s\n", fn_name); + if (dump_flags & TDF_DETAILS) + dump_function_to_file (node->decl, dump_file, dump_flags); + fprintf (dump_file, "\n"); + } + + candidate_callees.safe_push (callee); + } + + return !candidate_callees.is_empty (); +} + +bool +ipa_array_dse::find_array_dse_candidate_edges () +{ + if (dump_file) + fprintf (dump_file, "Finding array dse candidate call edges\n\n"); + + for (auto *callee : candidate_callees) + { + cgraph_edge *e = callee->node->callers; + while (e && e->caller == callee->node) + e = e->next_caller; + + for (auto *c : candidate_callees) + if (e->caller == c->node) + return false; + + auto *edge = new array_dse_edge (e, callee); + if (!edge->analyze ()) + { + delete edge; + continue; + } + + if (dump_file) + { + fprintf (dump_file, "Found candidate call edge: "); + print_gimple_stmt (dump_file, e->call_stmt, 0, TDF_NONE); + if (dump_flags & TDF_DETAILS) + dump_function_to_file (e->caller->decl, dump_file, dump_flags); + fprintf (dump_file, "\n"); + } + + candidate_edges.safe_push (edge); + } + + return !candidate_edges.is_empty (); +} + +bool +ipa_array_dse::apply_array_dse (array_dse_edge *ad_edge) +{ + /* Remove call stmt if it's fully redundant. */ + if (ad_edge->fully_redundant ()) + { + cgraph_node *caller = ad_edge->call_edge->caller; + gimple *call_stmt = ad_edge->call_edge->call_stmt; + + cfun_saver save (caller); + + auto gsi = gsi_for_stmt (call_stmt); + basic_block call_bb = gimple_bb (call_stmt); + tree fndecl = gimple_call_fndecl (call_stmt); + caller->remove_stmt_references (call_stmt); + unlink_stmt_vdef (call_stmt); + if (gsi_remove (&gsi, true)) + gimple_purge_dead_eh_edges (call_bb); + cgraph_update_edges_for_call_stmt (call_stmt, fndecl, nullptr); + + return true; + } + + /* Insert array redundant bound check to callee. */ + array_dse_callee *callee = ad_edge->callee; + cgraph_node *orig_callee = ad_edge->callee->node; + cgraph_node *new_callee + = orig_callee->create_version_clone_with_body (vNULL, NULL, NULL, NULL, + NULL, "array_dse", NULL); + + if (!transform_new_callee (callee, new_callee)) + return false; + + tree bound_addr = ad_edge->get_bound_addr (); + rewrite_call_edge (ad_edge->call_edge, new_callee, bound_addr); + + return true; +} + +tree +ipa_array_dse::add_bound_param (tree param) +{ + vec *new_params = NULL; + auto_vec arg_decls; + + push_function_arg_decls (&arg_decls, cfun->decl); + gcc_checking_assert (!arg_decls.is_empty ()); + vec_safe_reserve (new_params, arg_decls.length () + 1); + + for (unsigned i = 0; i < arg_decls.length (); ++i) + { + ipa_adjusted_param adj; + + memset (&adj, 0, sizeof (adj)); + + adj.type = TREE_TYPE (arg_decls[i]); + adj.base_index = i; + adj.prev_clone_index = i; + adj.op = IPA_PARAM_OP_COPY; + new_params->quick_push (adj); + } + + tree param_name = DECL_NAME (param); + const char *name = concat (IDENTIFIER_POINTER (param_name), ".bound", NULL); + ipa_adjusted_param adj; + adj.type = TREE_TYPE (param); + adj.base_index = arg_decls.length (); + adj.prev_clone_index = arg_decls.length (); + adj.op = IPA_PARAM_OP_NEW; + new_params->quick_push (adj); + + auto adjustments = new ipa_param_body_adjustments (new_params, cfun->decl); + adjustments->modify_formal_parameters (); + delete adjustments; + + arg_decls.truncate (0); + push_function_arg_decls (&arg_decls, cfun->decl); + + tree new_param = arg_decls.last (); + DECL_NAME (new_param) = get_identifier (name); + + return get_or_create_ssa_default_def (cfun, new_param); +} + +tree +ipa_array_dse::find_array_main_var (array_dse_callee *callee) +{ + int i = 0; + tree param = DECL_ARGUMENTS (cfun->decl); + while (i++ < callee->array_param_index) + param = DECL_CHAIN (param); + + tree name; + FOR_EACH_SSA_NAME (i, name, cfun) + { + if (!SSA_NAME_IS_DEFAULT_DEF (name) + || SSA_NAME_VAR (name) != param) + continue; + + if (!callee->main_loop) + return name; + + use_operand_p use_p; + gimple *stmt; + if (!single_imm_use (name, &use_p, &stmt) + || gimple_code (stmt) != GIMPLE_PHI) + return nullptr; + + return gimple_phi_result (stmt); + } + + return nullptr; +} + +bool +ipa_array_dse::transform_new_callee (array_dse_callee *callee, + cgraph_node *new_node) +{ + cfun_saver save (new_node); + + tree bound_ssa = add_bound_param (callee->array_param); + tree array = find_array_main_var (callee); + if (!array) + return false; + + edge e; + if (callee->main_loop) + { + gimple *array_def_stmt = SSA_NAME_DEF_STMT (array); + basic_block array_def_bb = gimple_bb (array_def_stmt); + gcc_assert (gimple_code (array_def_stmt) == GIMPLE_PHI); + e = split_block_after_labels (array_def_bb); + } + else + { + basic_block entry_bb = ENTRY_BLOCK_PTR_FOR_FN (cfun); + gcc_assert (single_succ_p (entry_bb)); + basic_block bb = split_edge (single_succ_edge (entry_bb)); + e = single_succ_edge (bb); + } + + auto gsi = gsi_last_bb (e->src); + gimple *cond = gimple_build_cond (GE_EXPR, array, bound_ssa, nullptr, + nullptr); + gsi_insert_after (&gsi, cond, GSI_NEW_STMT); + + edge return_edge = make_edge (e->src, EXIT_BLOCK_PTR_FOR_FN (cfun), 0); + basic_block return_bb = split_edge (return_edge); + auto return_gsi = gsi_last_bb (return_bb); + gsi_insert_after (&return_gsi, gimple_build_return (nullptr), GSI_NEW_STMT); + + e->flags &= ~EDGE_FALLTHRU; + e->flags |= EDGE_FALSE_VALUE; + single_pred_edge (return_bb)->flags |= EDGE_TRUE_VALUE; + single_succ_edge (return_bb)->flags = 0; + + for (auto call_edge = new_node->callees; call_edge; + call_edge = call_edge->next_callee) + rewrite_call_edge (call_edge, new_node, bound_ssa); + + return true; +} + +void +ipa_array_dse::rewrite_call_edge (cgraph_edge *edge, cgraph_node *new_node, + tree bound_addr) +{ + auto_vec args; + gcall *call_stmt = edge->call_stmt; + gimple_stmt_iterator gsi = gsi_for_stmt (call_stmt); + cgraph_node *caller = edge->caller; + cfun_saver save (caller); + + for (unsigned i = 0; i < gimple_call_num_args (call_stmt); i++) + args.safe_push (gimple_call_arg (call_stmt, i)); + + bound_addr = force_gimple_operand_gsi (&gsi, bound_addr, true, + NULL_TREE, true, + GSI_SAME_STMT); + args.safe_push (bound_addr); + + gcall *new_call = gimple_build_call_vec (new_node->decl, args); + + if (tree vdef = gimple_vdef (call_stmt)) + { + gimple_set_vdef (new_call, vdef); + SSA_NAME_DEF_STMT (vdef) = new_call; + } + + gimple_set_vuse (new_call, gimple_vuse (call_stmt)); + gimple_call_copy_flags (new_call, call_stmt); + gimple_call_set_chain (new_call, gimple_call_chain (call_stmt)); + gsi_replace (&gsi, new_call, false); + + cgraph_update_edges_for_call_stmt (call_stmt, + gimple_call_fndecl (call_stmt), new_call); + + caller->remove_stmt_references (call_stmt); + caller->record_stmt_references (new_call); +} + +} // namespace array_dse + +namespace { + +const pass_data pass_data_ipa_array_dse = +{ + SIMPLE_IPA_PASS, /* type */ + "array-dse", /* name */ + OPTGROUP_NONE, /* optinfo_flags */ + TV_IPA_ARRAY_DSE, /* tv_id */ + PROP_cfg | PROP_ssa, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + 0, /* todo_flags_finish */ +}; + +class pass_ipa_array_dse : public simple_ipa_opt_pass +{ +public: + pass_ipa_array_dse (gcc::context *ctxt) + : simple_ipa_opt_pass (pass_data_ipa_array_dse, ctxt) + {} + + /* opt_pass methods: */ + virtual bool gate (function *) + { + return optimize >= 3 && flag_ipa_array_dse; + } + + virtual unsigned int execute (function *) + { + return array_dse::ipa_array_dse ().execute (); + } + +}; // class pass_ipa_array_dse + +} // anon namespace + +simple_ipa_opt_pass * +make_pass_ipa_array_dse (gcc::context *ctxt) +{ + return new pass_ipa_array_dse (ctxt); +} diff --git a/gcc/ipa-array-dse.h b/gcc/ipa-array-dse.h new file mode 100644 index 00000000000..b1f5ee611d5 --- /dev/null +++ b/gcc/ipa-array-dse.h @@ -0,0 +1,263 @@ +/* Array dead store elimination + Copyright (C) 2021-2022 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#ifndef IPA_ARRAY_DSE_H +#define IPA_ARRAY_DSE_H + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "options.h" +#include "function.h" +#include "cfgloop.h" +#include "hash-map.h" +#include "tree-core.h" +#include "tree.h" +#include "bitmap.h" +#include "value-range.h" +#include + +namespace array_dse { + +enum compare_result +{ + COMPARE_ERROR = 0, + LT = 1, + EQ = 2, + GT = 4, + LE = LT | EQ, + GE = GT | EQ, + NE = LT | GT +}; + +enum access_kind +{ + NONE = 0, + READ = 1, + WRITE = 2, + ACCESS_ERROR = 4 +}; + +enum class infinite_kind +{ + NON_INF, + INF, + NINF +}; + +/* Address analyzer. */ +class addr_analyzer +{ +public: + tree get_address (tree var); + +private: + tree analyze_address (tree var); + +private: + hash_map address_map; +}; + +class array_dse_callee +{ +public: + array_dse_callee (cgraph_node *node); + + bool analyze (); + unsigned HOST_WIDE_INT get_len_param_max () const; + +private: + tree mult_tree (basic_block bb, tree t1, tree t2); + tree div_tree (basic_block bb, tree t1, tree t2); + tree lshift_tree (tree t1, tree t2); + tree rshift_tree (tree t1, tree t2); + tree max_tree (basic_block bb, tree t1, tree t2); + tree min_tree (basic_block bb, tree t1, tree t2); + HOST_WIDE_INT calc_tree_value (tree t, HOST_WIDE_INT n_value); + value_range calc_tree_range (basic_block bb, tree t); + bool get_factor (tree t, double &factor); + bool positive_factor_p (tree t); + value_range build_range (basic_block bb, tree_code op, + const value_range &r1, const value_range &r2); + compare_result compare_tree (basic_block bb, tree t1, tree t2); + compare_result compare_tree_by_minus (basic_block bb, tree t1, tree t2); + bool filter_function () const; + bool no_return_p () const; + bool find_main_vars (); + void find_tail_recursive_loop (tree *default_def); + bool find_candidate_array (); + bool find_length_param (); + void collect_read_write_ptrs (); + void calc_length_param_max (); + bool check_pointer (tree var, tree &len, unsigned &size); + bool check_offset (tree var, tree &len, unsigned &size, + auto_vec &worklist); + bool check_mult_expr (gimple *stmt, tree &len, unsigned &size); + int find_param_index (tree base); + bool check_array_usage (); + void calc_len_range (); + void update_len_range (basic_block start, const value_range &new_range); + value_range invert_range (const value_range &range) const; + value_range get_range_from_cond (gimple *stmt); + value_range get_var_range (basic_block bb, tree offset); + bool calc_ptr_range (); + bool check_ptr_range (); + bool check_recursive_call_arg (); + void collect_recursive_call_args (auto_vec &array_args, + auto_vec &len_args, + auto_vec &blocks, + auto_vec &is_tail_recursive_call); + void update_branch_range (basic_block bb); + tree build_recursive_offset (tree len); + tree build_recursive_ptr_range_max (basic_block bb, tree array, + tree offset); + bool tail_recursive_call_p (gimple *stmt); + bool return_bb_p (basic_block bb) const; + bool calc_loop_var_range (loop_p loop); + bool check_loop_exits (loop_p loop); + bool fill_loop_var_range (loop_p loop, tree_code code, tree lhs, + tree rhs, tree step); + bool fill_loop_ptr_range (loop_p loop, basic_block bb, + tree count_length); + bool loop_header_p (basic_block bb); + bool iterate_var_p (loop_p loop, tree var); + tree get_loop_var (loop_p loop, tree iterate_var); + bool find_var_range (tree var, basic_block bb); + +public: + cgraph_node *node = nullptr; + tree array_param = nullptr; + tree len_param = nullptr; + int array_param_index = -1; + int len_param_index = -1; + tree array_main_var = nullptr; + tree len_main_var = nullptr; + tree signed_len_var = nullptr; + tree elem_size = nullptr; + unsigned elem_size_cst = 0; + + loop_p main_loop = nullptr; + + static constexpr unsigned PARAM_NUM = 2; + +private: + addr_analyzer analyzer; + + hash_set all_ptrs; + hash_set visited_offset; + hash_map branch_start_map; + hash_map len_range_map; + std::map> var_range; + std::map> loop_ptrs; + hash_set unreachable_blocks; + + static constexpr unsigned HOST_WIDE_INT len_param_min = 1; + unsigned HOST_WIDE_INT len_param_max = 0; +}; + +class array_dse_edge +{ +public: + array_dse_edge (cgraph_edge *edge, array_dse_callee *callee); + + bool analyze (); + bool fully_redundant (); + tree get_bound_addr (); + +private: + bool find_local_array_from_arg (); + bool check_array_usage (); + bool collect_array_accesses (); + bool check_array_access (gimple *stmt, tree var); + bool check_array_address (gimple *stmt, tree addr); + bool check_array_address (gphi *phi, tree addr); + bool check_array_address (gassign *assign, tree addr); + bool check_access_from_address (tree addr); + bool check_access_kind (gimple *stmt, tree var); + access_kind check_access_kind_iterate (tree var, auto_bitmap &visited); + bool find_inner_array (); + bool unique_use_p (tree source, gimple *unique_assign) const; + bool initialize_assign_p (gimple *stmt) const; + bool calc_read_bound (); + value_range calc_ref_range (tree var); + value_range calc_addr_range (tree var); + value_range calc_offset_range (tree var); + value_range calc_simple_loop_range (tree var); + void collect_call_block_succs (); + bool calc_array_arg_start (); + bool check_optimized_area_rewrite (); + bool check_full_write_elem (basic_block bb, tree var); + bool array_index_of_addr_p (tree index, tree addr); + tree strip_init_var (tree var, tree last_var); + bool check_len_arg_range (); + bool check_len_arg_lower_bound (); + bool check_len_arg_upper_bound (); + bool after_call_stmt_p (gimple *stmt); + bool write_array_p (tree var); + bool read_array_p (tree var); + bool call_stmt_p (gimple *stmt) const; + bool array_ref_p (tree var); + bool array_addr_p (tree var); + +public: + cgraph_edge *call_edge = nullptr; + array_dse_callee *callee = nullptr; + + tree array = nullptr; + +private: + unsigned array_size = 0; + unsigned elem_size = 0; + tree inner_array = nullptr; + tree inner_elem_type = nullptr; + + hash_map array_accesses; + hash_map access_kinds; + hash_set array_address_vars; + hash_set call_block_succs; + + HOST_WIDE_INT read_upper_bound = 0; + HOST_WIDE_INT array_arg_start = 0; + HOST_WIDE_INT len_arg_min = 0; +}; + +class ipa_array_dse +{ +public: + unsigned execute (); + +private: + bool find_array_dse_candidate_callees (); + bool find_array_dse_candidate_edges (); + bool apply_array_dse (array_dse_edge *edge); + tree add_bound_param (tree param); + tree find_array_main_var (array_dse_callee *callee); + bool transform_new_callee (array_dse_callee *callee, cgraph_node *new_node); + void rewrite_call_edge (cgraph_edge *edge, cgraph_node *new_node, + tree bound_ssa); + +private: + auto_vec nodes; + auto_delete_vec candidate_callees; + auto_delete_vec candidate_edges; +}; + +} + +#endif diff --git a/gcc/ipa-localize-array.cc b/gcc/ipa-localize-array.cc new file mode 100644 index 00000000000..4678756b20a --- /dev/null +++ b/gcc/ipa-localize-array.cc @@ -0,0 +1,614 @@ +/* IPA optimization to transform global calloced array to be + specific function local. + Copyright (C) 2021-2022 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "backend.h" +#include "tree.h" +#include "gimple.h" +#include "gimple-iterator.h" +#include "ssa.h" +#include "tree-pass.h" +#include "tree-cfg.h" +#include "gimplify.h" +#include "gimple-pretty-print.h" +#include "tree-into-ssa.h" +#include "ipa-utils.h" +#include "fold-const.h" +#include "tree-dfa.h" +#include "cfgloop.h" + +class array_localizer +{ +public: + array_localizer (varpool_node *var); + void localize (); + +private: + bool scalar_type_p (tree type); + bool scalar_memop_p (tree ref_val, gimple *use_stmt); + bool stmt_dominated_by_p (enum cdi_direction dir, gimple *stmt0, + gimple *stmt1); + gimple *find_calloc_stmt (gimple *stmt); + gimple *find_free_stmt (tree var); + bool check_var_store (); + bool check_var_load (); + bool find_call_edge (); + void remove_referring_stmt (gimple *stmt); + void replace_store_with_ssa (gimple *stmt, tree var_ssa); + void replace_load_with_ssa (gimple *stmt, tree var_ssa); + gimple *copy_call_without_location (gimple *stmt); + void rewrite_array (); + void insert_new_init (tree var_ssa); + void insert_new_alloc_free (tree var_ssa); + void rewrite_access_in_callee (tree var_ssa); + void remove_orig_alloc_free (); + +private: + varpool_node *var = nullptr; + tree var_type = nullptr; + ipa_ref *alloc_ref = nullptr; + ipa_ref *free_ref = nullptr; + gimple *alloc_stmt = nullptr; + gimple *free_stmt = nullptr; + cgraph_node *caller = nullptr; + cgraph_node *callee = nullptr; + cgraph_edge *call_edge = nullptr; + gimple *call_stmt = nullptr; + + bool scalar_alloc_p = false; + + auto_vec removed_stmts; +}; + +array_localizer::array_localizer (varpool_node *var) + : var (var) +{ +} + +void +array_localizer::localize () +{ + if (DECL_EXTERNAL (var->decl) || var->in_other_partition + || !var->can_remove_if_no_refs_p ()) + return; + + var_type = TREE_TYPE (var->decl); + + /* Only care about pointer variable. */ + if (!POINTER_TYPE_P (var_type)) + return; + + if (!check_var_store () || !check_var_load ()) + return; + + if (callee->used_from_other_partition + || callee->cannot_return_p () + || callee->get_availability () != AVAIL_LOCAL + || callee->has_aliases_p ()) + return; + + if (!find_call_edge ()) + return; + + { + cfun_saver save (caller); + if (!stmt_dominated_by_p (CDI_DOMINATORS, free_stmt, alloc_stmt) + || !stmt_dominated_by_p (CDI_POST_DOMINATORS, alloc_stmt, free_stmt) + || !stmt_dominated_by_p (CDI_POST_DOMINATORS, alloc_stmt, call_stmt) + || !stmt_dominated_by_p (CDI_DOMINATORS, free_stmt, call_stmt)) + return; + } + + rewrite_array (); +} + +bool +array_localizer::scalar_type_p (tree type) +{ + if (INTEGRAL_TYPE_P (type) || POINTER_TYPE_P (type) + || SCALAR_FLOAT_TYPE_P (type)) + return true; + return false; +} + +bool +array_localizer::scalar_memop_p (tree ref_val, gimple *use_stmt) +{ + if (gimple_has_volatile_ops (use_stmt)) + return false; + + if (!gimple_assign_load_p (use_stmt) && !gimple_store_p (use_stmt)) + return false; + + tree type = TREE_TYPE (ref_val); + if (!POINTER_TYPE_P (type)) + return false; + + tree lhs = gimple_get_lhs (use_stmt); + tree rhs1 = gimple_assign_rhs1 (use_stmt); + tree memref = gimple_store_p (use_stmt) ? lhs : rhs1; + + HOST_WIDE_INT offset, size; + bool reverse; + memref = get_ref_base_and_extent_hwi (memref, &offset, &size, &reverse); + + if (!memref || offset || TREE_CODE (memref) != MEM_REF + || !operand_equal_p (TREE_OPERAND (memref, 0), ref_val) + || !integer_zerop (TREE_OPERAND (memref, 1)) + || !types_compatible_p (TREE_TYPE (lhs), TREE_TYPE (type))) + return false; + + /* Exclude address-escape case like *var = var */ + ssa_op_iter iter; + tree use = nullptr; + int use_count = 0; + FOR_EACH_SSA_TREE_OPERAND (use, use_stmt, iter, SSA_OP_USE) + if (operand_equal_p (use, ref_val) && use_count++) + return false; + + return true; +} + +bool +array_localizer::stmt_dominated_by_p (enum cdi_direction dir, gimple *stmt0, + gimple *stmt1) +{ + basic_block bb0 = gimple_bb (stmt0); + basic_block bb1 = gimple_bb (stmt1); + + if (bb0 == bb1) + { + renumber_gimple_stmt_uids_in_blocks (&bb0, 1); + + if (dir == CDI_DOMINATORS) + return stmt0->uid > stmt1->uid; + else + return stmt0->uid < stmt1->uid; + } + + return dominated_by_p (dir, bb0, bb1); +} + +gimple * +array_localizer::find_calloc_stmt (gimple *stmt) +{ + if (!gimple_assign_single_p (stmt)) + return nullptr; + + tree rhs = gimple_assign_rhs1 (stmt); + if (TREE_CODE (rhs) != SSA_NAME || !has_single_use (rhs)) + return nullptr; + + gimple *def_stmt = SSA_NAME_DEF_STMT (rhs); + if (!gimple_call_builtin_p (def_stmt, BUILT_IN_CALLOC)) + return nullptr; + + return def_stmt; +} + +gimple * +array_localizer::find_free_stmt (tree var) +{ + use_operand_p use_p = nullptr; + gimple *use_stmt = nullptr; + if (TREE_CODE (var) != SSA_NAME || !single_imm_use (var, &use_p, &use_stmt)) + return nullptr; + + if (!gimple_call_builtin_p (use_stmt, BUILT_IN_FREE)) + return nullptr; + + return use_stmt; +} + +bool +array_localizer::check_var_store () +{ + ipa_ref *ref = nullptr; + for (unsigned i = 0; var->iterate_referring (i, ref); i++) + { + cgraph_node *node = dyn_cast (ref->referring); + if (!node) + return false; + + if (!ref->stmt || gimple_has_volatile_ops (ref->stmt)) + return false; + + /* Only allow calloc. */ + if (ref->use == IPA_REF_STORE) + { + /* Multiple alloc is not supported yet. */ + if (alloc_ref) + return false; + + if (!gimple_store_p (ref->stmt) + || !operand_equal_p (var->decl, gimple_get_lhs (ref->stmt))) + return false; + + alloc_stmt = find_calloc_stmt (ref->stmt); + if (!alloc_stmt) + return false; + + tree arg0 = gimple_call_arg (alloc_stmt, 0); + tree arg1 = gimple_call_arg (alloc_stmt, 1); + if (TREE_CODE (arg0) != INTEGER_CST + || TREE_CODE (arg1) != INTEGER_CST) + return false; + + tree elem_size = TYPE_SIZE_UNIT (TREE_TYPE (var_type)); + if (scalar_type_p (TREE_TYPE (var_type)) + && integer_onep (arg0) + && tree_int_cst_equal (arg1, elem_size)) + scalar_alloc_p = true; + + alloc_ref = ref; + caller = node; + } + } + + return alloc_ref != nullptr; +} + +bool +array_localizer::check_var_load () +{ + ipa_ref *ref = nullptr; + for (unsigned i = 0; var->iterate_referring (i, ref); i++) + { + if (ref->use == IPA_REF_STORE) + continue; + + if (ref->use != IPA_REF_LOAD) + return false; + + if (!gimple_assign_load_p (ref->stmt) + || !operand_equal_p (var->decl, gimple_assign_rhs1 (ref->stmt))) + return false; + + tree lhs = gimple_assign_lhs (ref->stmt); + if (TREE_CODE (lhs) != SSA_NAME) + return false; + + if (!free_ref) + { + gimple *stmt = find_free_stmt (lhs); + if (stmt) + { + if (!operand_equal_p (gimple_call_arg (stmt, 0), lhs) + || ref->referring != caller) + return false; + + free_ref = ref; + free_stmt = stmt; + continue; + } + } + + gimple *use_stmt = nullptr; + imm_use_iterator iter; + FOR_EACH_IMM_USE_STMT (use_stmt, iter, lhs) + { + if (is_gimple_debug (use_stmt)) + continue; + + if (!is_gimple_assign (use_stmt)) + return false; + + if (scalar_alloc_p + && !scalar_memop_p (lhs, use_stmt)) + scalar_alloc_p = false; + + /* All other reference must be in the same callee. */ + cgraph_node *node = dyn_cast (ref->referring); + if (!node || (callee && callee != node)) + return false; + + callee = node; + } + } + + return callee && callee != caller; +} + +/* Now we only allow function that is called only once by other + function (non-recursive call). */ + +bool +array_localizer::find_call_edge () +{ + cgraph_edge *e = callee->callers; + if (!e || e->next_caller || e->caller != caller) + return false; + + call_edge = e; + call_stmt = e->call_stmt; + return true; +} + +void +array_localizer::remove_referring_stmt (gimple *stmt) +{ + gimple_stmt_iterator gsi; + + if (dump_file) + { + fprintf (dump_file, "Remove statement:\n"); + print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS); + fprintf (dump_file, "\n"); + } + + gsi = gsi_for_stmt (stmt); + unlink_stmt_vdef (stmt); + gsi_remove (&gsi, true); + release_defs (stmt); +} + +void +array_localizer::replace_store_with_ssa (gimple *stmt, tree var_ssa) +{ + if (dump_file) + { + fprintf (dump_file, "Update store statement:\n"); + print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS); + } + + create_new_def_for (var_ssa, stmt, NULL); + update_stmt (stmt); + + if (dump_file) + { + fprintf (dump_file, "->\n"); + print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS); + fprintf (dump_file, "\n"); + } +} + +void +array_localizer::replace_load_with_ssa (gimple *stmt, tree var_ssa) +{ + if (dump_file) + { + fprintf (dump_file, "Update load statement:\n"); + print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS); + } + + gimple_assign_set_rhs1 (stmt, var_ssa); + update_stmt (stmt); + + if (dump_file) + { + fprintf (dump_file, "->\n"); + print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS); + fprintf (dump_file, "\n"); + } +} + +gimple * +array_localizer::copy_call_without_location (gimple *stmt) +{ + tree callee = unshare_expr_without_location (gimple_call_fndecl (stmt)); + auto_vec args; + + for (unsigned i = 0; i < gimple_call_num_args (stmt); i++) + { + tree arg = gimple_call_arg (stmt, i); + args.safe_push (unshare_expr_without_location (arg)); + } + + return gimple_build_call_vec (callee, args); +} + +void +array_localizer::rewrite_array () +{ + if (dump_file) + { + fprintf (dump_file, "Localize global array: "); + print_generic_expr (dump_file, var->decl); + fprintf (dump_file, "\n\n"); + } + + cfun_saver save (callee); + + tree type = TREE_TYPE (scalar_alloc_p ? var_type : var->decl); + const char *name = get_name (var->decl); + tree var_ssa = make_temp_ssa_name (type, NULL, name ? name : ""); + + if (scalar_alloc_p) + insert_new_init (var_ssa); + else + insert_new_alloc_free (var_ssa); + + rewrite_access_in_callee (var_ssa); + remove_orig_alloc_free (); + + for (auto stmt : removed_stmts) + caller->remove_stmt_references (stmt); +} + +void +array_localizer::insert_new_init (tree var_ssa) +{ + tree init_value = build_zero_cst (TREE_TYPE (var_ssa)); + gimple *init = gimple_build_assign (var_ssa, init_value); + + basic_block entry_bb = single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)); + gimple_stmt_iterator gsi = gsi_start_nondebug_after_labels_bb (entry_bb); + gsi_insert_before (&gsi, init, GSI_SAME_STMT); +} + +void +array_localizer::insert_new_alloc_free (tree var_ssa) +{ + gimple *new_alloc = copy_call_without_location (alloc_stmt); + gimple *new_free = copy_call_without_location (free_stmt); + basic_block entry_bb = single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)); + gimple_stmt_iterator gsi = gsi_start_nondebug_after_labels_bb (entry_bb); + + gimple_set_lhs (new_alloc, var_ssa); + gsi_insert_before (&gsi, new_alloc, GSI_SAME_STMT); + + if (dump_file) + { + fprintf (dump_file, "Insert calloc statement:\n"); + print_gimple_stmt (dump_file, new_alloc, 0, TDF_VOPS); + fprintf (dump_file, "\n"); + } + + bool free_used = false; + for (auto e : EXIT_BLOCK_PTR_FOR_FN (cfun)->preds) + { + gimple *stmt = last_stmt (e->src); + if (gimple_code (stmt) != GIMPLE_RETURN) + continue; + + if (free_used) + new_free = gimple_copy (new_free); + else + free_used = true; + + auto gsi = gsi_for_stmt (stmt); + gimple_call_set_arg (new_free, 0, var_ssa); + gsi_insert_before (&gsi, new_free, GSI_SAME_STMT); + + if (dump_file) + { + fprintf (dump_file, "Insert free statement:\n"); + print_gimple_stmt (dump_file, new_free, 0, TDF_VOPS); + fprintf (dump_file, "\n"); + } + } +} + +void +array_localizer::rewrite_access_in_callee (tree var_ssa) +{ + ipa_ref *ref = nullptr; + for (unsigned i = 0; var->iterate_referring (i, ref); i++) + { + if (ref == alloc_ref || ref == free_ref) + continue; + + gcc_assert (ref->referring == callee && ref->use == IPA_REF_LOAD); + + if (scalar_alloc_p) + { + tree lhs = gimple_assign_lhs (ref->stmt); + gimple *use_stmt = nullptr; + imm_use_iterator iter; + FOR_EACH_IMM_USE_STMT (use_stmt, iter, lhs) + { + if (is_gimple_debug (use_stmt)) + remove_referring_stmt (use_stmt); + else if (gimple_store_p (use_stmt)) + replace_store_with_ssa (use_stmt, var_ssa); + else if (gimple_assign_load_p (use_stmt)) + replace_load_with_ssa (use_stmt, var_ssa); + else + gcc_unreachable (); + } + remove_referring_stmt (ref->stmt); + } + else + replace_load_with_ssa (ref->stmt, var_ssa); + + removed_stmts.safe_push (ref->stmt); + } + + update_ssa (TODO_update_ssa); +} + +void +array_localizer::remove_orig_alloc_free () +{ + cfun_saver save (caller); + + /* Remove calloc() and free(). */ + remove_referring_stmt (alloc_stmt); + remove_referring_stmt (alloc_ref->stmt); + remove_referring_stmt (free_stmt); + remove_referring_stmt (free_ref->stmt); + removed_stmts.safe_push (alloc_ref->stmt); + removed_stmts.safe_push (free_ref->stmt); + + update_ssa (TODO_update_ssa); +} + +/* Execute the driver for IPA variable localization. */ + +static unsigned int +ipa_localize_array (void) +{ + cgraph_node *node = nullptr; + FOR_EACH_FUNCTION (node) + { + if (!node->real_symbol_p () || !node->definition + || !node->has_gimple_body_p () || node->inlined_to) + continue; + node->get_body (); + } + + varpool_node *var = nullptr; + FOR_EACH_VARIABLE (var) + array_localizer (var).localize (); + + return 0; +} + +namespace { + +const pass_data pass_data_ipa_localize_array = +{ + SIMPLE_IPA_PASS, /* type */ + "localize-array", /* name */ + OPTGROUP_NONE, /* optinfo_flags */ + TV_IPA_LOCALIZE_ARRAY, /* tv_id */ + 0, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + 0, /* todo_flags_finish */ +}; + +class pass_ipa_localize_array : public simple_ipa_opt_pass +{ +public: + pass_ipa_localize_array (gcc::context *ctxt) + : simple_ipa_opt_pass (pass_data_ipa_localize_array, ctxt) + {} + + /* opt_pass methods: */ + virtual bool gate (function *) + { + return optimize >= 3 && flag_ipa_localize_array; + } + + virtual unsigned int execute (function *) { return ipa_localize_array (); } + +}; // class pass_ipa_localize_array + +} // anon namespace + +simple_ipa_opt_pass * +make_pass_ipa_localize_array (gcc::context *ctxt) +{ + return new pass_ipa_localize_array (ctxt); +} diff --git a/gcc/ipa-struct-reorg/ipa-struct-reorg.cc b/gcc/ipa-struct-reorg/ipa-struct-reorg.cc index 18b41eb1b6a..851bda65cd5 100644 --- a/gcc/ipa-struct-reorg/ipa-struct-reorg.cc +++ b/gcc/ipa-struct-reorg/ipa-struct-reorg.cc @@ -4356,6 +4356,14 @@ ipa_struct_reorg::find_vars (gimple *stmt) find_var (gimple_assign_rhs1 (stmt), stmt); find_var (gimple_assign_rhs2 (stmt), stmt); } + else if (gimple_assign_rhs_code (stmt) == EQ_EXPR + && types_compatible_p ( + TYPE_MAIN_VARIANT (TREE_TYPE (gimple_assign_rhs1 (stmt))), + TYPE_MAIN_VARIANT (TREE_TYPE (gimple_assign_rhs2 (stmt))))) + { + find_var (gimple_assign_rhs1 (stmt), stmt); + find_var (gimple_assign_rhs2 (stmt), stmt); + } else { /* Because we won't handle these stmts in rewrite phase, @@ -8543,6 +8551,33 @@ ipa_struct_reorg::rewrite_assign (gassign *stmt, gimple_stmt_iterator *gsi) return remove; } + if (gimple_assign_cast_p (stmt)) + { + tree rhs = gimple_assign_rhs1 (stmt); + tree newrhs[max_split]; + if (!rewrite_expr (rhs, newrhs)) + return false; + + if (dump_file && (dump_flags & TDF_DETAILS)) + { + fprintf (dump_file, "\nrewriting cast statement:\n"); + print_gimple_stmt (dump_file, stmt, 0); + } + + tree lhs = gimple_assign_lhs (stmt); + tree conv_rhs = fold_convert (TREE_TYPE (lhs), newrhs[0]); + gimple *newstmt = gimple_build_assign (lhs, conv_rhs); + gsi_insert_before (gsi, newstmt, GSI_SAME_STMT); + + if (dump_file && (dump_flags & TDF_DETAILS)) + { + fprintf (dump_file, "replaced with:\n"); + print_gimple_stmt (dump_file, newstmt, 0); + fprintf (dump_file, "\n"); + } + return true; + } + return remove; } @@ -11203,7 +11238,7 @@ ipa_struct_reorg::find_fc_paths (fc_type_info *info) { /* Already seen. */ if (srfn->fc_path.start_stmt) - return false; + return srfn->fc_path.start_stmt == start_stmt; SET_CFUN (srfn); diff --git a/gcc/ipa-utils.cc b/gcc/ipa-utils.cc index 67dd42f4faf..5d9981c0420 100644 --- a/gcc/ipa-utils.cc +++ b/gcc/ipa-utils.cc @@ -35,6 +35,30 @@ along with GCC; see the file COPYING3. If not see #include "tree-vrp.h" #include "ipa-prop.h" #include "ipa-fnsummary.h" +#include "cfgloop.h" + +cfun_saver::cfun_saver (cgraph_node *node) +{ + push_cfun (DECL_STRUCT_FUNCTION (node->decl)); + calculate_dominance_info (CDI_DOMINATORS); + calculate_dominance_info (CDI_POST_DOMINATORS); +} + +cfun_saver::cfun_saver (cgraph_node *node, unsigned loop_flags) + : cfun_saver (node) +{ + loop_optimizer_init (loop_flags); + need_finalize_loop_optimizer = true; +} + +cfun_saver::~cfun_saver () +{ + if (need_finalize_loop_optimizer) + loop_optimizer_finalize (); + free_dominance_info (CDI_POST_DOMINATORS); + free_dominance_info (CDI_DOMINATORS); + pop_cfun (); +} /* Debugging function for postorder and inorder code. NOTE is a string that is printed before the nodes are printed. ORDER is an array of @@ -781,3 +805,23 @@ recursive_call_p (tree func, tree dest) return false; return true; } + +/* Return true if NODE has only one non-recursive caller and no non-recursive + callee. */ +bool +leaf_recursive_node_p (cgraph_node *node) +{ + if (node->inlined_to || !node->has_gimple_body_p () || node->indirect_calls) + return false; + + for (cgraph_edge *e = node->callees; e; e = e->next_callee) + if (node != e->callee) + return false; + + unsigned non_recursive_caller_count = 0; + for (cgraph_edge *e = node->callers; e; e = e->next_caller) + if (node != e->caller) + non_recursive_caller_count++; + + return non_recursive_caller_count == 1; +} diff --git a/gcc/ipa-utils.h b/gcc/ipa-utils.h index dc6ba0d52cb..15c63e9055a 100644 --- a/gcc/ipa-utils.h +++ b/gcc/ipa-utils.h @@ -21,6 +21,9 @@ along with GCC; see the file COPYING3. If not see #ifndef GCC_IPA_UTILS_H #define GCC_IPA_UTILS_H +#include "cgraph.h" +#include "function.h" + struct ipa_dfs_info { int dfn_number; int low_link; @@ -33,6 +36,17 @@ struct ipa_dfs_info { PTR aux; }; +/* Use RAII to help save cfun. */ +class cfun_saver +{ +public: + cfun_saver (cgraph_node *node); + cfun_saver (cgraph_node *node, unsigned loop_flags); + ~cfun_saver (); + +private: + bool need_finalize_loop_optimizer = false; +}; /* In ipa-utils.cc */ void ipa_print_order (FILE*, const char *, struct cgraph_node**, int); @@ -46,6 +60,7 @@ tree get_base_var (tree); void ipa_merge_profiles (struct cgraph_node *dst, struct cgraph_node *src, bool preserve_body = false); bool recursive_call_p (tree, tree); +bool leaf_recursive_node_p (cgraph_node *node); /* In ipa-pure-const.cc */ bool finite_function_p (); diff --git a/gcc/passes.def b/gcc/passes.def index 08213f2bc11..431f9c7c94b 100644 --- a/gcc/passes.def +++ b/gcc/passes.def @@ -184,6 +184,20 @@ along with GCC; see the file COPYING3. If not see passes are executed after partitioning and thus see just parts of the compiled unit. */ INSERT_PASSES_AFTER (all_late_ipa_passes) + NEXT_PASS (pass_ipa_alignment_propagation); + PUSH_INSERT_PASSES_WITHIN (pass_ipa_alignment_propagation) + NEXT_PASS (pass_ccp, true /* nonzero_p */); + NEXT_PASS (pass_early_vrp); + NEXT_PASS (pass_cd_dce); + NEXT_PASS (pass_forwprop); + NEXT_PASS (pass_rebuild_cgraph_edges); + POP_INSERT_PASSES () + NEXT_PASS (pass_ipa_localize_array); + PUSH_INSERT_PASSES_WITHIN (pass_ipa_localize_array) + NEXT_PASS (pass_forwprop); + NEXT_PASS (pass_rebuild_cgraph_edges); + POP_INSERT_PASSES () + NEXT_PASS (pass_ipa_array_dse); NEXT_PASS (pass_ipa_hardware_detection); NEXT_PASS (pass_ipa_pta); /* FIXME: this should be a normal IP pass. */ diff --git a/gcc/timevar.def b/gcc/timevar.def index 14129a500b1..4d3e54f7599 100644 --- a/gcc/timevar.def +++ b/gcc/timevar.def @@ -81,6 +81,9 @@ DEFTIMEVAR (TV_IPA_CONSTANT_PROP , "ipa cp") DEFTIMEVAR (TV_IPA_INLINING , "ipa inlining heuristics") DEFTIMEVAR (TV_IPA_FNSPLIT , "ipa function splitting") DEFTIMEVAR (TV_IPA_COMDATS , "ipa comdats") +DEFTIMEVAR (TV_IPA_ALIGNMENT_PROPAGATION, "ipa alignment propagation") +DEFTIMEVAR (TV_IPA_LOCALIZE_ARRAY , "ipa localize array") +DEFTIMEVAR (TV_IPA_ARRAY_DSE , "ipa array dse") DEFTIMEVAR (TV_IPA_HARDWARE_DETECTION, "ipa detection") DEFTIMEVAR (TV_IPA_PREFETCH , "ipa prefetch") DEFTIMEVAR (TV_IPA_STRUCT_REORG , "ipa struct reorg optimization") diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h index e01d86fb136..e89220303e1 100644 --- a/gcc/tree-pass.h +++ b/gcc/tree-pass.h @@ -534,6 +534,9 @@ extern ipa_opt_pass_d *make_pass_ipa_icp (gcc::context *ctxt); extern ipa_opt_pass_d *make_pass_ipa_odr (gcc::context *ctxt); extern ipa_opt_pass_d *make_pass_ipa_reference (gcc::context *ctxt); extern ipa_opt_pass_d *make_pass_ipa_pure_const (gcc::context *ctxt); +extern simple_ipa_opt_pass *make_pass_ipa_alignment_propagation (gcc::context *ctxt); +extern simple_ipa_opt_pass *make_pass_ipa_localize_array (gcc::context *ctxt); +extern simple_ipa_opt_pass *make_pass_ipa_array_dse (gcc::context *ctxt); extern simple_ipa_opt_pass *make_pass_ipa_hardware_detection (gcc::context * ctxt); extern simple_ipa_opt_pass *make_pass_ipa_prefetch (gcc::context *ctxt); -- Gitee