From 18ab4042ce75c2176f7133ea107e26c010be9481 Mon Sep 17 00:00:00 2001 From: huzife <634763349@qq.com> Date: Tue, 20 May 2025 19:10:14 +0800 Subject: [PATCH] [Sync] Sync patches from openeuler/gcc (cherry picked from commit 26d537981ea51f0f0b3ca2023b610f1d202c7743) --- ...4OE_oeAware-section-dup-in-namespace.patch | 55 + ...propagation-localize-array-array-dse.patch | 4997 +++++++++++++++++ gcc.spec | 10 +- 3 files changed, 5061 insertions(+), 1 deletion(-) create mode 100644 0377-oeAware-Fix-.GCC4OE_oeAware-section-dup-in-namespace.patch create mode 100644 0378-Add-alignment-propagation-localize-array-array-dse.patch diff --git a/0377-oeAware-Fix-.GCC4OE_oeAware-section-dup-in-namespace.patch b/0377-oeAware-Fix-.GCC4OE_oeAware-section-dup-in-namespace.patch new file mode 100644 index 0000000..bc78518 --- /dev/null +++ b/0377-oeAware-Fix-.GCC4OE_oeAware-section-dup-in-namespace.patch @@ -0,0 +1,55 @@ +From 81600a0743fd889363339bc1463a56bae84cda60 Mon Sep 17 00:00:00 2001 +From: liyancheng <412998149@qq.com> +Date: Sun, 18 May 2025 11:12:12 +0800 +Subject: [PATCH 1/2] [oeAware] Fix .GCC4OE_oeAware section dup in namespace + main + +This resolves an ICE "section already exists" caused +by incorrectly creating the .GCC4OE_oeAware section +for non-global main functions. +--- + gcc/testsuite/gcc.dg/oeaware-main-in-namespace.cpp | 10 ++++++++++ + gcc/varasm.cc | 8 +++++--- + 2 files changed, 15 insertions(+), 3 deletions(-) + create mode 100644 gcc/testsuite/gcc.dg/oeaware-main-in-namespace.cpp + +diff --git a/gcc/testsuite/gcc.dg/oeaware-main-in-namespace.cpp b/gcc/testsuite/gcc.dg/oeaware-main-in-namespace.cpp +new file mode 100644 +index 000000000..5e44f4a1c +--- /dev/null ++++ b/gcc/testsuite/gcc.dg/oeaware-main-in-namespace.cpp +@@ -0,0 +1,10 @@ ++/* { dg-do compile { target *-*-linux* *-*-gnu* } } */ ++/* { dg-options "-foeaware-policy=1" } */ ++ ++namespace radar8446940 { ++int main () { ++ return 0; ++} ++} ++ ++/* { dg-final { scan-assembler-not "GCC4OE_oeAware" } } */ +diff --git a/gcc/varasm.cc b/gcc/varasm.cc +index bdf02edea..5134c0c1f 100644 +--- a/gcc/varasm.cc ++++ b/gcc/varasm.cc +@@ -8572,11 +8572,13 @@ handle_vtv_comdat_section (section *sect, const_tree decl ATTRIBUTE_UNUSED) + void + create_oeaware_section () + { +- /* To prevent inserting repeated segments and data, +- we only perform the insertion in the file where the main ++ /* To prevent inserting repeated segments and data, we only perform ++ the insertion in the file where the GLOBAL main + function is located. */ + if (!cfun || TREE_CODE (cfun->decl) != FUNCTION_DECL +- || !DECL_NAME (cfun->decl) || !MAIN_NAME_P (DECL_NAME (cfun->decl))) ++ || !DECL_NAME (cfun->decl) || !MAIN_NAME_P (DECL_NAME (cfun->decl)) ++ || (DECL_CONTEXT (cfun->decl) != NULL_TREE && ++ TREE_CODE (DECL_CONTEXT (cfun->decl)) != TRANSLATION_UNIT_DECL)) + return; + + int flags = SECTION_STRINGS; +-- +2.33.0 + diff --git a/0378-Add-alignment-propagation-localize-array-array-dse.patch b/0378-Add-alignment-propagation-localize-array-array-dse.patch new file mode 100644 index 0000000..3b5a056 --- /dev/null +++ b/0378-Add-alignment-propagation-localize-array-array-dse.patch @@ -0,0 +1,4997 @@ +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 2/2] 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 c7a503235..070e5e456 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 4cd2574e4..ed4696b7a 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 80242ddf8..faa445e26 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 000000000..3f14818ae +--- /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 000000000..df973e849 +--- /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 000000000..b1f5ee611 +--- /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 000000000..4678756b2 +--- /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 18b41eb1b..851bda65c 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 67dd42f4f..5d9981c04 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 dc6ba0d52..15c63e905 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 08213f2bc..431f9c7c9 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 14129a500..4d3e54f75 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 e01d86fb1..e89220303 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); +-- +2.33.0 + diff --git a/gcc.spec b/gcc.spec index 19dd263..8f5090e 100644 --- a/gcc.spec +++ b/gcc.spec @@ -2,7 +2,7 @@ %global gcc_major 12 # Note, gcc_release must be integer, if you want to add suffixes to # %%{release}, append them after %%{gcc_release} on Release: line. -%global gcc_release 82 +%global gcc_release 83 %global _unpackaged_files_terminate_build 0 %global _performance_build 1 @@ -486,6 +486,8 @@ Patch373: 0373-Include-insn-opinit.h-in-PLUGIN_H-PR110610.patch Patch374: 0374-Add-hip12-instructions-pipeline.patch Patch375: 0375-SVE-Fix-gcc-cross-compile-error.patch Patch376: 0376-Struct-dynamic-field-compression-optimization.patch +Patch377: 0377-oeAware-Fix-.GCC4OE_oeAware-section-dup-in-namespace.patch +Patch378: 0378-Add-alignment-propagation-localize-array-array-dse.patch # Part 1001-1999 %ifarch sw_64 @@ -1648,6 +1650,8 @@ not stable, so plugins must be rebuilt any time GCC is updated. %patch -P374 -p1 %patch -P375 -p1 %patch -P376 -p1 +%patch -P377 -p1 +%patch -P378 -p1 %ifarch sw_64 %patch -P1001 -p1 @@ -4275,6 +4279,10 @@ end %doc rpm.doc/changelogs/libcc1/ChangeLog* %changelog +* Tue May 20 2025 huzife <634763349@qq.com> - 12.3.1-83 +- Type: Sync +- DESC: Sync patches from openeuler/gcc. + * Wed May 14 2025 huzife <634763349@qq.com> - 12.3.1-82 - Type: Sync - DESC: Add struct dynamic-field-compression optimization -- Gitee