| /* Transformation Utilities for Loop Vectorization. |
| Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 |
| Free Software Foundation, Inc. |
| Contributed by Dorit Naishlos <dorit@il.ibm.com> |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it under |
| the terms of the GNU General Public License as published by the Free |
| Software Foundation; either version 3, or (at your option) any later |
| version. |
| |
| GCC is distributed in the hope that it will be useful, but WITHOUT ANY |
| WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include "ggc.h" |
| #include "tree.h" |
| #include "target.h" |
| #include "rtl.h" |
| #include "basic-block.h" |
| #include "diagnostic.h" |
| #include "tree-flow.h" |
| #include "tree-dump.h" |
| #include "timevar.h" |
| #include "cfgloop.h" |
| #include "expr.h" |
| #include "optabs.h" |
| #include "params.h" |
| #include "recog.h" |
| #include "tree-data-ref.h" |
| #include "tree-chrec.h" |
| #include "tree-scalar-evolution.h" |
| #include "tree-vectorizer.h" |
| #include "langhooks.h" |
| #include "tree-pass.h" |
| #include "toplev.h" |
| #include "real.h" |
| |
| /* Utility functions for the code transformation. */ |
| static bool vect_transform_stmt (gimple, gimple_stmt_iterator *, bool *, |
| slp_tree, slp_instance); |
| static tree vect_create_destination_var (tree, tree); |
| static tree vect_create_data_ref_ptr |
| (gimple, struct loop*, tree, tree *, gimple *, bool, bool *, tree); |
| static tree vect_create_addr_base_for_vector_ref |
| (gimple, gimple_seq *, tree, struct loop *); |
| static tree vect_get_new_vect_var (tree, enum vect_var_kind, const char *); |
| static tree vect_get_vec_def_for_operand (tree, gimple, tree *); |
| static tree vect_init_vector (gimple, tree, tree, gimple_stmt_iterator *); |
| static void vect_finish_stmt_generation |
| (gimple stmt, gimple vec_stmt, gimple_stmt_iterator *); |
| static bool vect_is_simple_cond (tree, loop_vec_info); |
| static void vect_create_epilog_for_reduction |
| (tree, gimple, int, enum tree_code, gimple); |
| static tree get_initial_def_for_reduction (gimple, tree, tree *); |
| |
| /* Utility function dealing with loop peeling (not peeling itself). */ |
| static void vect_generate_tmps_on_preheader |
| (loop_vec_info, tree *, tree *, tree *); |
| static tree vect_build_loop_niters (loop_vec_info); |
| static void vect_update_ivs_after_vectorizer (loop_vec_info, tree, edge); |
| static tree vect_gen_niters_for_prolog_loop (loop_vec_info, tree); |
| static void vect_update_init_of_dr (struct data_reference *, tree niters); |
| static void vect_update_inits_of_drs (loop_vec_info, tree); |
| static int vect_min_worthwhile_factor (enum tree_code); |
| |
| |
| static int |
| cost_for_stmt (gimple stmt) |
| { |
| stmt_vec_info stmt_info = vinfo_for_stmt (stmt); |
| |
| switch (STMT_VINFO_TYPE (stmt_info)) |
| { |
| case load_vec_info_type: |
| return TARG_SCALAR_LOAD_COST; |
| case store_vec_info_type: |
| return TARG_SCALAR_STORE_COST; |
| case op_vec_info_type: |
| case condition_vec_info_type: |
| case assignment_vec_info_type: |
| case reduc_vec_info_type: |
| case induc_vec_info_type: |
| case type_promotion_vec_info_type: |
| case type_demotion_vec_info_type: |
| case type_conversion_vec_info_type: |
| case call_vec_info_type: |
| return TARG_SCALAR_STMT_COST; |
| case undef_vec_info_type: |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| |
| /* Function vect_estimate_min_profitable_iters |
| |
| Return the number of iterations required for the vector version of the |
| loop to be profitable relative to the cost of the scalar version of the |
| loop. |
| |
| TODO: Take profile info into account before making vectorization |
| decisions, if available. */ |
| |
| int |
| vect_estimate_min_profitable_iters (loop_vec_info loop_vinfo) |
| { |
| int i; |
| int min_profitable_iters; |
| int peel_iters_prologue; |
| int peel_iters_epilogue; |
| int vec_inside_cost = 0; |
| int vec_outside_cost = 0; |
| int scalar_single_iter_cost = 0; |
| int scalar_outside_cost = 0; |
| int vf = LOOP_VINFO_VECT_FACTOR (loop_vinfo); |
| struct loop *loop = LOOP_VINFO_LOOP (loop_vinfo); |
| basic_block *bbs = LOOP_VINFO_BBS (loop_vinfo); |
| int nbbs = loop->num_nodes; |
| int byte_misalign = LOOP_PEELING_FOR_ALIGNMENT (loop_vinfo); |
| int peel_guard_costs = 0; |
| int innerloop_iters = 0, factor; |
| VEC (slp_instance, heap) *slp_instances; |
| slp_instance instance; |
| |
| /* Cost model disabled. */ |
| if (!flag_vect_cost_model) |
| { |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "cost model disabled."); |
| return 0; |
| } |
| |
| /* Requires loop versioning tests to handle misalignment. */ |
| if (VEC_length (gimple, LOOP_VINFO_MAY_MISALIGN_STMTS (loop_vinfo))) |
| { |
| /* FIXME: Make cost depend on complexity of individual check. */ |
| vec_outside_cost += |
| VEC_length (gimple, LOOP_VINFO_MAY_MISALIGN_STMTS (loop_vinfo)); |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "cost model: Adding cost of checks for loop " |
| "versioning to treat misalignment.\n"); |
| } |
| |
| if (VEC_length (ddr_p, LOOP_VINFO_MAY_ALIAS_DDRS (loop_vinfo))) |
| { |
| /* FIXME: Make cost depend on complexity of individual check. */ |
| vec_outside_cost += |
| VEC_length (ddr_p, LOOP_VINFO_MAY_ALIAS_DDRS (loop_vinfo)); |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "cost model: Adding cost of checks for loop " |
| "versioning aliasing.\n"); |
| } |
| |
| if (VEC_length (gimple, LOOP_VINFO_MAY_MISALIGN_STMTS (loop_vinfo)) |
| || VEC_length (ddr_p, LOOP_VINFO_MAY_ALIAS_DDRS (loop_vinfo))) |
| { |
| vec_outside_cost += TARG_COND_TAKEN_BRANCH_COST; |
| } |
| |
| /* Count statements in scalar loop. Using this as scalar cost for a single |
| iteration for now. |
| |
| TODO: Add outer loop support. |
| |
| TODO: Consider assigning different costs to different scalar |
| statements. */ |
| |
| /* FORNOW. */ |
| if (loop->inner) |
| innerloop_iters = 50; /* FIXME */ |
| |
| for (i = 0; i < nbbs; i++) |
| { |
| gimple_stmt_iterator si; |
| basic_block bb = bbs[i]; |
| |
| if (bb->loop_father == loop->inner) |
| factor = innerloop_iters; |
| else |
| factor = 1; |
| |
| for (si = gsi_start_bb (bb); !gsi_end_p (si); gsi_next (&si)) |
| { |
| gimple stmt = gsi_stmt (si); |
| stmt_vec_info stmt_info = vinfo_for_stmt (stmt); |
| /* Skip stmts that are not vectorized inside the loop. */ |
| if (!STMT_VINFO_RELEVANT_P (stmt_info) |
| && (!STMT_VINFO_LIVE_P (stmt_info) |
| || STMT_VINFO_DEF_TYPE (stmt_info) != vect_reduction_def)) |
| continue; |
| scalar_single_iter_cost += cost_for_stmt (stmt) * factor; |
| vec_inside_cost += STMT_VINFO_INSIDE_OF_LOOP_COST (stmt_info) * factor; |
| /* FIXME: for stmts in the inner-loop in outer-loop vectorization, |
| some of the "outside" costs are generated inside the outer-loop. */ |
| vec_outside_cost += STMT_VINFO_OUTSIDE_OF_LOOP_COST (stmt_info); |
| } |
| } |
| |
| /* Add additional cost for the peeled instructions in prologue and epilogue |
| loop. |
| |
| FORNOW: If we don't know the value of peel_iters for prologue or epilogue |
| at compile-time - we assume it's vf/2 (the worst would be vf-1). |
| |
| TODO: Build an expression that represents peel_iters for prologue and |
| epilogue to be used in a run-time test. */ |
| |
| if (byte_misalign < 0) |
| { |
| peel_iters_prologue = vf/2; |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "cost model: " |
| "prologue peel iters set to vf/2."); |
| |
| /* If peeling for alignment is unknown, loop bound of main loop becomes |
| unknown. */ |
| peel_iters_epilogue = vf/2; |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "cost model: " |
| "epilogue peel iters set to vf/2 because " |
| "peeling for alignment is unknown ."); |
| |
| /* If peeled iterations are unknown, count a taken branch and a not taken |
| branch per peeled loop. Even if scalar loop iterations are known, |
| vector iterations are not known since peeled prologue iterations are |
| not known. Hence guards remain the same. */ |
| peel_guard_costs += 2 * (TARG_COND_TAKEN_BRANCH_COST |
| + TARG_COND_NOT_TAKEN_BRANCH_COST); |
| } |
| else |
| { |
| if (byte_misalign) |
| { |
| struct data_reference *dr = LOOP_VINFO_UNALIGNED_DR (loop_vinfo); |
| int element_size = GET_MODE_SIZE (TYPE_MODE (TREE_TYPE (DR_REF (dr)))); |
| tree vectype = STMT_VINFO_VECTYPE (vinfo_for_stmt (DR_STMT (dr))); |
| int nelements = TYPE_VECTOR_SUBPARTS (vectype); |
| |
| peel_iters_prologue = nelements - (byte_misalign / element_size); |
| } |
| else |
| peel_iters_prologue = 0; |
| |
| if (!LOOP_VINFO_NITERS_KNOWN_P (loop_vinfo)) |
| { |
| peel_iters_epilogue = vf/2; |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "cost model: " |
| "epilogue peel iters set to vf/2 because " |
| "loop iterations are unknown ."); |
| |
| /* If peeled iterations are known but number of scalar loop |
| iterations are unknown, count a taken branch per peeled loop. */ |
| peel_guard_costs += 2 * TARG_COND_TAKEN_BRANCH_COST; |
| |
| } |
| else |
| { |
| int niters = LOOP_VINFO_INT_NITERS (loop_vinfo); |
| peel_iters_prologue = niters < peel_iters_prologue ? |
| niters : peel_iters_prologue; |
| peel_iters_epilogue = (niters - peel_iters_prologue) % vf; |
| } |
| } |
| |
| vec_outside_cost += (peel_iters_prologue * scalar_single_iter_cost) |
| + (peel_iters_epilogue * scalar_single_iter_cost) |
| + peel_guard_costs; |
| |
| /* FORNOW: The scalar outside cost is incremented in one of the |
| following ways: |
| |
| 1. The vectorizer checks for alignment and aliasing and generates |
| a condition that allows dynamic vectorization. A cost model |
| check is ANDED with the versioning condition. Hence scalar code |
| path now has the added cost of the versioning check. |
| |
| if (cost > th & versioning_check) |
| jmp to vector code |
| |
| Hence run-time scalar is incremented by not-taken branch cost. |
| |
| 2. The vectorizer then checks if a prologue is required. If the |
| cost model check was not done before during versioning, it has to |
| be done before the prologue check. |
| |
| if (cost <= th) |
| prologue = scalar_iters |
| if (prologue == 0) |
| jmp to vector code |
| else |
| execute prologue |
| if (prologue == num_iters) |
| go to exit |
| |
| Hence the run-time scalar cost is incremented by a taken branch, |
| plus a not-taken branch, plus a taken branch cost. |
| |
| 3. The vectorizer then checks if an epilogue is required. If the |
| cost model check was not done before during prologue check, it |
| has to be done with the epilogue check. |
| |
| if (prologue == 0) |
| jmp to vector code |
| else |
| execute prologue |
| if (prologue == num_iters) |
| go to exit |
| vector code: |
| if ((cost <= th) | (scalar_iters-prologue-epilogue == 0)) |
| jmp to epilogue |
| |
| Hence the run-time scalar cost should be incremented by 2 taken |
| branches. |
| |
| TODO: The back end may reorder the BBS's differently and reverse |
| conditions/branch directions. Change the estimates below to |
| something more reasonable. */ |
| |
| /* If the number of iterations is known and we do not do versioning, we can |
| decide whether to vectorize at compile time. Hence the scalar version |
| do not carry cost model guard costs. */ |
| if (!LOOP_VINFO_NITERS_KNOWN_P (loop_vinfo) |
| || VEC_length (gimple, LOOP_VINFO_MAY_MISALIGN_STMTS (loop_vinfo)) |
| || VEC_length (ddr_p, LOOP_VINFO_MAY_ALIAS_DDRS (loop_vinfo))) |
| { |
| /* Cost model check occurs at versioning. */ |
| if (VEC_length (gimple, LOOP_VINFO_MAY_MISALIGN_STMTS (loop_vinfo)) |
| || VEC_length (ddr_p, LOOP_VINFO_MAY_ALIAS_DDRS (loop_vinfo))) |
| scalar_outside_cost += TARG_COND_NOT_TAKEN_BRANCH_COST; |
| else |
| { |
| /* Cost model check occurs at prologue generation. */ |
| if (LOOP_PEELING_FOR_ALIGNMENT (loop_vinfo) < 0) |
| scalar_outside_cost += 2 * TARG_COND_TAKEN_BRANCH_COST |
| + TARG_COND_NOT_TAKEN_BRANCH_COST; |
| /* Cost model check occurs at epilogue generation. */ |
| else |
| scalar_outside_cost += 2 * TARG_COND_TAKEN_BRANCH_COST; |
| } |
| } |
| |
| /* Add SLP costs. */ |
| slp_instances = LOOP_VINFO_SLP_INSTANCES (loop_vinfo); |
| for (i = 0; VEC_iterate (slp_instance, slp_instances, i, instance); i++) |
| { |
| vec_outside_cost += SLP_INSTANCE_OUTSIDE_OF_LOOP_COST (instance); |
| vec_inside_cost += SLP_INSTANCE_INSIDE_OF_LOOP_COST (instance); |
| } |
| |
| /* Calculate number of iterations required to make the vector version |
| profitable, relative to the loop bodies only. The following condition |
| must hold true: |
| SIC * niters + SOC > VIC * ((niters-PL_ITERS-EP_ITERS)/VF) + VOC |
| where |
| SIC = scalar iteration cost, VIC = vector iteration cost, |
| VOC = vector outside cost, VF = vectorization factor, |
| PL_ITERS = prologue iterations, EP_ITERS= epilogue iterations |
| SOC = scalar outside cost for run time cost model check. */ |
| |
| if ((scalar_single_iter_cost * vf) > vec_inside_cost) |
| { |
| if (vec_outside_cost <= 0) |
| min_profitable_iters = 1; |
| else |
| { |
| min_profitable_iters = ((vec_outside_cost - scalar_outside_cost) * vf |
| - vec_inside_cost * peel_iters_prologue |
| - vec_inside_cost * peel_iters_epilogue) |
| / ((scalar_single_iter_cost * vf) |
| - vec_inside_cost); |
| |
| if ((scalar_single_iter_cost * vf * min_profitable_iters) |
| <= ((vec_inside_cost * min_profitable_iters) |
| + ((vec_outside_cost - scalar_outside_cost) * vf))) |
| min_profitable_iters++; |
| } |
| } |
| /* vector version will never be profitable. */ |
| else |
| { |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "cost model: vector iteration cost = %d " |
| "is divisible by scalar iteration cost = %d by a factor " |
| "greater than or equal to the vectorization factor = %d .", |
| vec_inside_cost, scalar_single_iter_cost, vf); |
| return -1; |
| } |
| |
| if (vect_print_dump_info (REPORT_COST)) |
| { |
| fprintf (vect_dump, "Cost model analysis: \n"); |
| fprintf (vect_dump, " Vector inside of loop cost: %d\n", |
| vec_inside_cost); |
| fprintf (vect_dump, " Vector outside of loop cost: %d\n", |
| vec_outside_cost); |
| fprintf (vect_dump, " Scalar iteration cost: %d\n", |
| scalar_single_iter_cost); |
| fprintf (vect_dump, " Scalar outside cost: %d\n", scalar_outside_cost); |
| fprintf (vect_dump, " prologue iterations: %d\n", |
| peel_iters_prologue); |
| fprintf (vect_dump, " epilogue iterations: %d\n", |
| peel_iters_epilogue); |
| fprintf (vect_dump, " Calculated minimum iters for profitability: %d\n", |
| min_profitable_iters); |
| } |
| |
| min_profitable_iters = |
| min_profitable_iters < vf ? vf : min_profitable_iters; |
| |
| /* Because the condition we create is: |
| if (niters <= min_profitable_iters) |
| then skip the vectorized loop. */ |
| min_profitable_iters--; |
| |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, " Profitability threshold = %d\n", |
| min_profitable_iters); |
| |
| return min_profitable_iters; |
| } |
| |
| |
| /* TODO: Close dependency between vect_model_*_cost and vectorizable_* |
| functions. Design better to avoid maintenance issues. */ |
| |
| /* Function vect_model_reduction_cost. |
| |
| Models cost for a reduction operation, including the vector ops |
| generated within the strip-mine loop, the initial definition before |
| the loop, and the epilogue code that must be generated. */ |
| |
| static bool |
| vect_model_reduction_cost (stmt_vec_info stmt_info, enum tree_code reduc_code, |
| int ncopies) |
| { |
| int outer_cost = 0; |
| enum tree_code code; |
| optab optab; |
| tree vectype; |
| gimple stmt, orig_stmt; |
| tree reduction_op; |
| enum machine_mode mode; |
| loop_vec_info loop_vinfo = STMT_VINFO_LOOP_VINFO (stmt_info); |
| struct loop *loop = LOOP_VINFO_LOOP (loop_vinfo); |
| |
| |
| /* Cost of reduction op inside loop. */ |
| STMT_VINFO_INSIDE_OF_LOOP_COST (stmt_info) += ncopies * TARG_VEC_STMT_COST; |
| |
| stmt = STMT_VINFO_STMT (stmt_info); |
| |
| switch (get_gimple_rhs_class (gimple_assign_rhs_code (stmt))) |
| { |
| case GIMPLE_SINGLE_RHS: |
| gcc_assert (TREE_OPERAND_LENGTH (gimple_assign_rhs1 (stmt)) == ternary_op); |
| reduction_op = TREE_OPERAND (gimple_assign_rhs1 (stmt), 2); |
| break; |
| case GIMPLE_UNARY_RHS: |
| reduction_op = gimple_assign_rhs1 (stmt); |
| break; |
| case GIMPLE_BINARY_RHS: |
| reduction_op = gimple_assign_rhs2 (stmt); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| |
| vectype = get_vectype_for_scalar_type (TREE_TYPE (reduction_op)); |
| if (!vectype) |
| { |
| if (vect_print_dump_info (REPORT_COST)) |
| { |
| fprintf (vect_dump, "unsupported data-type "); |
| print_generic_expr (vect_dump, TREE_TYPE (reduction_op), TDF_SLIM); |
| } |
| return false; |
| } |
| |
| mode = TYPE_MODE (vectype); |
| orig_stmt = STMT_VINFO_RELATED_STMT (stmt_info); |
| |
| if (!orig_stmt) |
| orig_stmt = STMT_VINFO_STMT (stmt_info); |
| |
| code = gimple_assign_rhs_code (orig_stmt); |
| |
| /* Add in cost for initial definition. */ |
| outer_cost += TARG_SCALAR_TO_VEC_COST; |
| |
| /* Determine cost of epilogue code. |
| |
| We have a reduction operator that will reduce the vector in one statement. |
| Also requires scalar extract. */ |
| |
| if (!nested_in_vect_loop_p (loop, orig_stmt)) |
| { |
| if (reduc_code < NUM_TREE_CODES) |
| outer_cost += TARG_VEC_STMT_COST + TARG_VEC_TO_SCALAR_COST; |
| else |
| { |
| int vec_size_in_bits = tree_low_cst (TYPE_SIZE (vectype), 1); |
| tree bitsize = |
| TYPE_SIZE (TREE_TYPE (gimple_assign_lhs (orig_stmt))); |
| int element_bitsize = tree_low_cst (bitsize, 1); |
| int nelements = vec_size_in_bits / element_bitsize; |
| |
| optab = optab_for_tree_code (code, vectype, optab_default); |
| |
| /* We have a whole vector shift available. */ |
| if (VECTOR_MODE_P (mode) |
| && optab_handler (optab, mode)->insn_code != CODE_FOR_nothing |
| && optab_handler (vec_shr_optab, mode)->insn_code != CODE_FOR_nothing) |
| /* Final reduction via vector shifts and the reduction operator. Also |
| requires scalar extract. */ |
| outer_cost += ((exact_log2(nelements) * 2) * TARG_VEC_STMT_COST |
| + TARG_VEC_TO_SCALAR_COST); |
| else |
| /* Use extracts and reduction op for final reduction. For N elements, |
| we have N extracts and N-1 reduction ops. */ |
| outer_cost += ((nelements + nelements - 1) * TARG_VEC_STMT_COST); |
| } |
| } |
| |
| STMT_VINFO_OUTSIDE_OF_LOOP_COST (stmt_info) = outer_cost; |
| |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "vect_model_reduction_cost: inside_cost = %d, " |
| "outside_cost = %d .", STMT_VINFO_INSIDE_OF_LOOP_COST (stmt_info), |
| STMT_VINFO_OUTSIDE_OF_LOOP_COST (stmt_info)); |
| |
| return true; |
| } |
| |
| |
| /* Function vect_model_induction_cost. |
| |
| Models cost for induction operations. */ |
| |
| static void |
| vect_model_induction_cost (stmt_vec_info stmt_info, int ncopies) |
| { |
| /* loop cost for vec_loop. */ |
| STMT_VINFO_INSIDE_OF_LOOP_COST (stmt_info) = ncopies * TARG_VEC_STMT_COST; |
| /* prologue cost for vec_init and vec_step. */ |
| STMT_VINFO_OUTSIDE_OF_LOOP_COST (stmt_info) = 2 * TARG_SCALAR_TO_VEC_COST; |
| |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "vect_model_induction_cost: inside_cost = %d, " |
| "outside_cost = %d .", STMT_VINFO_INSIDE_OF_LOOP_COST (stmt_info), |
| STMT_VINFO_OUTSIDE_OF_LOOP_COST (stmt_info)); |
| } |
| |
| |
| /* Function vect_model_simple_cost. |
| |
| Models cost for simple operations, i.e. those that only emit ncopies of a |
| single op. Right now, this does not account for multiple insns that could |
| be generated for the single vector op. We will handle that shortly. */ |
| |
| void |
| vect_model_simple_cost (stmt_vec_info stmt_info, int ncopies, |
| enum vect_def_type *dt, slp_tree slp_node) |
| { |
| int i; |
| int inside_cost = 0, outside_cost = 0; |
| |
| /* The SLP costs were already calculated during SLP tree build. */ |
| if (PURE_SLP_STMT (stmt_info)) |
| return; |
| |
| inside_cost = ncopies * TARG_VEC_STMT_COST; |
| |
| /* FORNOW: Assuming maximum 2 args per stmts. */ |
| for (i = 0; i < 2; i++) |
| { |
| if (dt[i] == vect_constant_def || dt[i] == vect_invariant_def) |
| outside_cost += TARG_SCALAR_TO_VEC_COST; |
| } |
| |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "vect_model_simple_cost: inside_cost = %d, " |
| "outside_cost = %d .", inside_cost, outside_cost); |
| |
| /* Set the costs either in STMT_INFO or SLP_NODE (if exists). */ |
| stmt_vinfo_set_inside_of_loop_cost (stmt_info, slp_node, inside_cost); |
| stmt_vinfo_set_outside_of_loop_cost (stmt_info, slp_node, outside_cost); |
| } |
| |
| |
| /* Function vect_cost_strided_group_size |
| |
| For strided load or store, return the group_size only if it is the first |
| load or store of a group, else return 1. This ensures that group size is |
| only returned once per group. */ |
| |
| static int |
| vect_cost_strided_group_size (stmt_vec_info stmt_info) |
| { |
| gimple first_stmt = DR_GROUP_FIRST_DR (stmt_info); |
| |
| if (first_stmt == STMT_VINFO_STMT (stmt_info)) |
| return DR_GROUP_SIZE (stmt_info); |
| |
| return 1; |
| } |
| |
| |
| /* Function vect_model_store_cost |
| |
| Models cost for stores. In the case of strided accesses, one access |
| has the overhead of the strided access attributed to it. */ |
| |
| void |
| vect_model_store_cost (stmt_vec_info stmt_info, int ncopies, |
| enum vect_def_type dt, slp_tree slp_node) |
| { |
| int group_size; |
| int inside_cost = 0, outside_cost = 0; |
| |
| /* The SLP costs were already calculated during SLP tree build. */ |
| if (PURE_SLP_STMT (stmt_info)) |
| return; |
| |
| if (dt == vect_constant_def || dt == vect_invariant_def) |
| outside_cost = TARG_SCALAR_TO_VEC_COST; |
| |
| /* Strided access? */ |
| if (DR_GROUP_FIRST_DR (stmt_info) && !slp_node) |
| group_size = vect_cost_strided_group_size (stmt_info); |
| /* Not a strided access. */ |
| else |
| group_size = 1; |
| |
| /* Is this an access in a group of stores, which provide strided access? |
| If so, add in the cost of the permutes. */ |
| if (group_size > 1) |
| { |
| /* Uses a high and low interleave operation for each needed permute. */ |
| inside_cost = ncopies * exact_log2(group_size) * group_size |
| * TARG_VEC_STMT_COST; |
| |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "vect_model_store_cost: strided group_size = %d .", |
| group_size); |
| |
| } |
| |
| /* Costs of the stores. */ |
| inside_cost += ncopies * TARG_VEC_STORE_COST; |
| |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "vect_model_store_cost: inside_cost = %d, " |
| "outside_cost = %d .", inside_cost, outside_cost); |
| |
| /* Set the costs either in STMT_INFO or SLP_NODE (if exists). */ |
| stmt_vinfo_set_inside_of_loop_cost (stmt_info, slp_node, inside_cost); |
| stmt_vinfo_set_outside_of_loop_cost (stmt_info, slp_node, outside_cost); |
| } |
| |
| |
| /* Function vect_model_load_cost |
| |
| Models cost for loads. In the case of strided accesses, the last access |
| has the overhead of the strided access attributed to it. Since unaligned |
| accesses are supported for loads, we also account for the costs of the |
| access scheme chosen. */ |
| |
| void |
| vect_model_load_cost (stmt_vec_info stmt_info, int ncopies, slp_tree slp_node) |
| |
| { |
| int group_size; |
| int alignment_support_cheme; |
| gimple first_stmt; |
| struct data_reference *dr = STMT_VINFO_DATA_REF (stmt_info), *first_dr; |
| int inside_cost = 0, outside_cost = 0; |
| |
| /* The SLP costs were already calculated during SLP tree build. */ |
| if (PURE_SLP_STMT (stmt_info)) |
| return; |
| |
| /* Strided accesses? */ |
| first_stmt = DR_GROUP_FIRST_DR (stmt_info); |
| if (first_stmt && !slp_node) |
| { |
| group_size = vect_cost_strided_group_size (stmt_info); |
| first_dr = STMT_VINFO_DATA_REF (vinfo_for_stmt (first_stmt)); |
| } |
| /* Not a strided access. */ |
| else |
| { |
| group_size = 1; |
| first_dr = dr; |
| } |
| |
| alignment_support_cheme = vect_supportable_dr_alignment (first_dr); |
| |
| /* Is this an access in a group of loads providing strided access? |
| If so, add in the cost of the permutes. */ |
| if (group_size > 1) |
| { |
| /* Uses an even and odd extract operations for each needed permute. */ |
| inside_cost = ncopies * exact_log2(group_size) * group_size |
| * TARG_VEC_STMT_COST; |
| |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "vect_model_load_cost: strided group_size = %d .", |
| group_size); |
| |
| } |
| |
| /* The loads themselves. */ |
| switch (alignment_support_cheme) |
| { |
| case dr_aligned: |
| { |
| inside_cost += ncopies * TARG_VEC_LOAD_COST; |
| |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "vect_model_load_cost: aligned."); |
| |
| break; |
| } |
| case dr_unaligned_supported: |
| { |
| /* Here, we assign an additional cost for the unaligned load. */ |
| inside_cost += ncopies * TARG_VEC_UNALIGNED_LOAD_COST; |
| |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "vect_model_load_cost: unaligned supported by " |
| "hardware."); |
| |
| break; |
| } |
| case dr_explicit_realign: |
| { |
| inside_cost += ncopies * (2*TARG_VEC_LOAD_COST + TARG_VEC_STMT_COST); |
| |
| /* FIXME: If the misalignment remains fixed across the iterations of |
| the containing loop, the following cost should be added to the |
| outside costs. */ |
| if (targetm.vectorize.builtin_mask_for_load) |
| inside_cost += TARG_VEC_STMT_COST; |
| |
| break; |
| } |
| case dr_explicit_realign_optimized: |
| { |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "vect_model_load_cost: unaligned software " |
| "pipelined."); |
| |
| /* Unaligned software pipeline has a load of an address, an initial |
| load, and possibly a mask operation to "prime" the loop. However, |
| if this is an access in a group of loads, which provide strided |
| access, then the above cost should only be considered for one |
| access in the group. Inside the loop, there is a load op |
| and a realignment op. */ |
| |
| if ((!DR_GROUP_FIRST_DR (stmt_info)) || group_size > 1 || slp_node) |
| { |
| outside_cost = 2*TARG_VEC_STMT_COST; |
| if (targetm.vectorize.builtin_mask_for_load) |
| outside_cost += TARG_VEC_STMT_COST; |
| } |
| |
| inside_cost += ncopies * (TARG_VEC_LOAD_COST + TARG_VEC_STMT_COST); |
| |
| break; |
| } |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| if (vect_print_dump_info (REPORT_COST)) |
| fprintf (vect_dump, "vect_model_load_cost: inside_cost = %d, " |
| "outside_cost = %d .", inside_cost, outside_cost); |
| |
| /* Set the costs either in STMT_INFO or SLP_NODE (if exists). */ |
| stmt_vinfo_set_inside_of_loop_cost (stmt_info, slp_node, inside_cost); |
| stmt_vinfo_set_outside_of_loop_cost (stmt_info, slp_node, outside_cost); |
| } |
| |
| |
| /* Function vect_get_new_vect_var. |
| |
| Returns a name for a new variable. The current naming scheme appends the |
| prefix "vect_" or "vect_p" (depending on the value of VAR_KIND) to |
| the name of vectorizer generated variables, and appends that to NAME if |
| provided. */ |
| |
| static tree |
| vect_get_new_vect_var (tree type, enum vect_var_kind var_kind, const char *name) |
| { |
| const char *prefix; |
| tree new_vect_var; |
| |
| switch (var_kind) |
| { |
| case vect_simple_var: |
| prefix = "vect_"; |
| break; |
| case vect_scalar_var: |
| prefix = "stmp_"; |
| break; |
| case vect_pointer_var: |
| prefix = "vect_p"; |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| |
| if (name) |
| { |
| char* tmp = concat (prefix, name, NULL); |
| new_vect_var = create_tmp_var (type, tmp); |
| free (tmp); |
| } |
| else |
| new_vect_var = create_tmp_var (type, prefix); |
| |
| /* Mark vector typed variable as a gimple register variable. */ |
| if (TREE_CODE (type) == VECTOR_TYPE) |
| DECL_GIMPLE_REG_P (new_vect_var) = true; |
| |
| return new_vect_var; |
| } |
| |
| |
| /* Function vect_create_addr_base_for_vector_ref. |
| |
| Create an expression that computes the address of the first memory location |
| that will be accessed for a data reference. |
| |
| Input: |
| STMT: The statement containing the data reference. |
| NEW_STMT_LIST: Must be initialized to NULL_TREE or a statement list. |
| OFFSET: Optional. If supplied, it is be added to the initial address. |
| LOOP: Specify relative to which loop-nest should the address be computed. |
| For example, when the dataref is in an inner-loop nested in an |
| outer-loop that is now being vectorized, LOOP can be either the |
| outer-loop, or the inner-loop. The first memory location accessed |
| by the following dataref ('in' points to short): |
| |
| for (i=0; i<N; i++) |
| for (j=0; j<M; j++) |
| s += in[i+j] |
| |
| is as follows: |
| if LOOP=i_loop: &in (relative to i_loop) |
| if LOOP=j_loop: &in+i*2B (relative to j_loop) |
| |
| Output: |
| 1. Return an SSA_NAME whose value is the address of the memory location of |
| the first vector of the data reference. |
| 2. If new_stmt_list is not NULL_TREE after return then the caller must insert |
| these statement(s) which define the returned SSA_NAME. |
| |
| FORNOW: We are only handling array accesses with step 1. */ |
| |
| static tree |
| vect_create_addr_base_for_vector_ref (gimple stmt, |
| gimple_seq *new_stmt_list, |
| tree offset, |
| struct loop *loop) |
| { |
| stmt_vec_info stmt_info = vinfo_for_stmt (stmt); |
| struct data_reference *dr = STMT_VINFO_DATA_REF (stmt_info); |
| struct loop *containing_loop = (gimple_bb (stmt))->loop_father; |
| tree data_ref_base = unshare_expr (DR_BASE_ADDRESS (dr)); |
| tree base_name; |
| tree data_ref_base_var; |
| tree vec_stmt; |
| tree addr_base, addr_expr; |
| tree dest; |
| gimple_seq seq = NULL; |
| tree base_offset = unshare_expr (DR_OFFSET (dr)); |
| tree init = unshare_expr (DR_INIT (dr)); |
| tree vect_ptr_type, addr_expr2; |
| tree step = TYPE_SIZE_UNIT (TREE_TYPE (DR_REF (dr))); |
| |
| gcc_assert (loop); |
| if (loop != containing_loop) |
| { |
| loop_vec_info loop_vinfo = STMT_VINFO_LOOP_VINFO (stmt_info); |
| struct loop *loop = LOOP_VINFO_LOOP (loop_vinfo); |
| |
| gcc_assert (nested_in_vect_loop_p (loop, stmt)); |
| |
| data_ref_base = unshare_expr (STMT_VINFO_DR_BASE_ADDRESS (stmt_info)); |
| base_offset = unshare_expr (STMT_VINFO_DR_OFFSET (stmt_info)); |
| init = unshare_expr (STMT_VINFO_DR_INIT (stmt_info)); |
| } |
| |
| /* Create data_ref_base */ |
| base_name = build_fold_indirect_ref (data_ref_base); |
| data_ref_base_var = create_tmp_var (TREE_TYPE (data_ref_base), "batmp"); |
| add_referenced_var (data_ref_base_var); |
| data_ref_base = force_gimple_operand (data_ref_base, &seq, true, |
| data_ref_base_var); |
| gimple_seq_add_seq (new_stmt_list, seq); |
| |
| /* Create base_offset */ |
| base_offset = size_binop (PLUS_EXPR, |
| fold_convert (sizetype, base_offset), |
| fold_convert (sizetype, init)); |
| dest = create_tmp_var (sizetype, "base_off"); |
| add_referenced_var (dest); |
| base_offset = force_gimple_operand (base_offset, &seq, true, dest); |
| gimple_seq_add_seq (new_stmt_list, seq); |
| |
| if (offset) |
| { |
| tree tmp = create_tmp_var (sizetype, "offset"); |
| |
| add_referenced_var (tmp); |
| offset = fold_build2 (MULT_EXPR, sizetype, |
| fold_convert (sizetype, offset), step); |
| base_offset = fold_build2 (PLUS_EXPR, sizetype, |
| base_offset, offset); |
| base_offset = force_gimple_operand (base_offset, &seq, false, tmp); |
| gimple_seq_add_seq (new_stmt_list, seq); |
| } |
| |
| /* base + base_offset */ |
| addr_base = fold_build2 (POINTER_PLUS_EXPR, TREE_TYPE (data_ref_base), |
| data_ref_base, base_offset); |
| |
| vect_ptr_type = build_pointer_type (STMT_VINFO_VECTYPE (stmt_info)); |
| |
| /* addr_expr = addr_base */ |
| addr_expr = vect_get_new_vect_var (vect_ptr_type, vect_pointer_var, |
| get_name (base_name)); |
| add_referenced_var (addr_expr); |
| vec_stmt = fold_convert (vect_ptr_type, addr_base); |
| addr_expr2 = vect_get_new_vect_var (vect_ptr_type, vect_pointer_var, |
| get_name (base_name)); |
| add_referenced_var (addr_expr2); |
| vec_stmt = force_gimple_operand (vec_stmt, &seq, false, addr_expr2); |
| gimple_seq_add_seq (new_stmt_list, seq); |
| |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| { |
| fprintf (vect_dump, "created "); |
| print_generic_expr (vect_dump, vec_stmt, TDF_SLIM); |
| } |
| return vec_stmt; |
| } |
| |
| |
| /* Function vect_create_data_ref_ptr. |
| |
| Create a new pointer to vector type (vp), that points to the first location |
| accessed in the loop by STMT, along with the def-use update chain to |
| appropriately advance the pointer through the loop iterations. Also set |
| aliasing information for the pointer. This vector pointer is used by the |
| callers to this function to create a memory reference expression for vector |
| load/store access. |
| |
| Input: |
| 1. STMT: a stmt that references memory. Expected to be of the form |
| GIMPLE_ASSIGN <name, data-ref> or |
| GIMPLE_ASSIGN <data-ref, name>. |
| 2. AT_LOOP: the loop where the vector memref is to be created. |
| 3. OFFSET (optional): an offset to be added to the initial address accessed |
| by the data-ref in STMT. |
| 4. ONLY_INIT: indicate if vp is to be updated in the loop, or remain |
| pointing to the initial address. |
| 5. TYPE: if not NULL indicates the required type of the data-ref. |
| |
| Output: |
| 1. Declare a new ptr to vector_type, and have it point to the base of the |
| data reference (initial addressed accessed by the data reference). |
| For example, for vector of type V8HI, the following code is generated: |
| |
| v8hi *vp; |
| vp = (v8hi *)initial_address; |
| |
| if OFFSET is not supplied: |
| initial_address = &a[init]; |
| if OFFSET is supplied: |
| initial_address = &a[init + OFFSET]; |
| |
| Return the initial_address in INITIAL_ADDRESS. |
| |
| 2. If ONLY_INIT is true, just return the initial pointer. Otherwise, also |
| update the pointer in each iteration of the loop. |
| |
| Return the increment stmt that updates the pointer in PTR_INCR. |
| |
| 3. Set INV_P to true if the access pattern of the data reference in the |
| vectorized loop is invariant. Set it to false otherwise. |
| |
| 4. Return the pointer. */ |
| |
| static tree |
| vect_create_data_ref_ptr (gimple stmt, struct loop *at_loop, |
| tree offset, tree *initial_address, gimple *ptr_incr, |
| bool only_init, bool *inv_p, tree type) |
| { |
| tree base_name; |
| stmt_vec_info stmt_info = vinfo_for_stmt (stmt); |
| loop_vec_info loop_vinfo = STMT_VINFO_LOOP_VINFO (stmt_info); |
| struct loop *loop = LOOP_VINFO_LOOP (loop_vinfo); |
| bool nested_in_vect_loop = nested_in_vect_loop_p (loop, stmt); |
| struct loop *containing_loop = (gimple_bb (stmt))->loop_father; |
| tree vectype = STMT_VINFO_VECTYPE (stmt_info); |
| tree vect_ptr_type; |
| tree vect_ptr; |
| tree tag; |
| tree new_temp; |
| gimple vec_stmt; |
| gimple_seq new_stmt_list = NULL; |
| edge pe; |
| basic_block new_bb; |
| tree vect_ptr_init; |
| struct data_reference *dr = STMT_VINFO_DATA_REF (stmt_info); |
| tree vptr; |
| gimple_stmt_iterator incr_gsi; |
| bool insert_after; |
| tree indx_before_incr, indx_after_incr; |
| gimple incr; |
| tree step; |
| |
| /* Check the step (evolution) of the load in LOOP, and record |
| whether it's invariant. */ |
| if (nested_in_vect_loop) |
| step = STMT_VINFO_DR_STEP (stmt_info); |
| else |
| step = DR_STEP (STMT_VINFO_DATA_REF (stmt_info)); |
| |
| if (tree_int_cst_compare (step, size_zero_node) == 0) |
| *inv_p = true; |
| else |
| *inv_p = false; |
| |
| /* Create an expression for the first address accessed by this load |
| in LOOP. */ |
| base_name = build_fold_indirect_ref (unshare_expr (DR_BASE_ADDRESS (dr))); |
| |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| { |
| tree data_ref_base = base_name; |
| fprintf (vect_dump, "create vector-pointer variable to type: "); |
| print_generic_expr (vect_dump, vectype, TDF_SLIM); |
| if (TREE_CODE (data_ref_base) == VAR_DECL) |
| fprintf (vect_dump, " vectorizing a one dimensional array ref: "); |
| else if (TREE_CODE (data_ref_base) == ARRAY_REF) |
| fprintf (vect_dump, " vectorizing a multidimensional array ref: "); |
| else if (TREE_CODE (data_ref_base) == COMPONENT_REF) |
| fprintf (vect_dump, " vectorizing a record based array ref: "); |
| else if (TREE_CODE (data_ref_base) == SSA_NAME) |
| fprintf (vect_dump, " vectorizing a pointer ref: "); |
| print_generic_expr (vect_dump, base_name, TDF_SLIM); |
| } |
| |
| /** (1) Create the new vector-pointer variable: **/ |
| if (type) |
| vect_ptr_type = build_pointer_type (type); |
| else |
| vect_ptr_type = build_pointer_type (vectype); |
| |
| if (TREE_CODE (DR_BASE_ADDRESS (dr)) == SSA_NAME |
| && TYPE_RESTRICT (TREE_TYPE (DR_BASE_ADDRESS (dr)))) |
| vect_ptr_type = build_qualified_type (vect_ptr_type, TYPE_QUAL_RESTRICT); |
| vect_ptr = vect_get_new_vect_var (vect_ptr_type, vect_pointer_var, |
| get_name (base_name)); |
| if (TREE_CODE (DR_BASE_ADDRESS (dr)) == SSA_NAME |
| && TYPE_RESTRICT (TREE_TYPE (DR_BASE_ADDRESS (dr)))) |
| { |
| get_alias_set (base_name); |
| DECL_POINTER_ALIAS_SET (vect_ptr) |
| = DECL_POINTER_ALIAS_SET (SSA_NAME_VAR (DR_BASE_ADDRESS (dr))); |
| } |
| |
| add_referenced_var (vect_ptr); |
| |
| /** (2) Add aliasing information to the new vector-pointer: |
| (The points-to info (DR_PTR_INFO) may be defined later.) **/ |
| |
| tag = DR_SYMBOL_TAG (dr); |
| gcc_assert (tag); |
| |
| /* If tag is a variable (and NOT_A_TAG) than a new symbol memory |
| tag must be created with tag added to its may alias list. */ |
| if (!MTAG_P (tag)) |
| new_type_alias (vect_ptr, tag, DR_REF (dr)); |
| else |
| { |
| set_symbol_mem_tag (vect_ptr, tag); |
| mark_sym_for_renaming (tag); |
| } |
| |
| /** Note: If the dataref is in an inner-loop nested in LOOP, and we are |
| vectorizing LOOP (i.e. outer-loop vectorization), we need to create two |
| def-use update cycles for the pointer: One relative to the outer-loop |
| (LOOP), which is what steps (3) and (4) below do. The other is relative |
| to the inner-loop (which is the inner-most loop containing the dataref), |
| and this is done be step (5) below. |
| |
| When vectorizing inner-most loops, the vectorized loop (LOOP) is also the |
| inner-most loop, and so steps (3),(4) work the same, and step (5) is |
| redundant. Steps (3),(4) create the following: |
| |
| vp0 = &base_addr; |
| LOOP: vp1 = phi(vp0,vp2) |
| ... |
| ... |
| vp2 = vp1 + step |
| goto LOOP |
| |
| If there is an inner-loop nested in loop, then step (5) will also be |
| applied, and an additional update in the inner-loop will be created: |
| |
| vp0 = &base_addr; |
| LOOP: vp1 = phi(vp0,vp2) |
| ... |
| inner: vp3 = phi(vp1,vp4) |
| vp4 = vp3 + inner_step |
| if () goto inner |
| ... |
| vp2 = vp1 + step |
| if () goto LOOP */ |
| |
| /** (3) Calculate the initial address the vector-pointer, and set |
| the vector-pointer to point to it before the loop: **/ |
| |
| /* Create: (&(base[init_val+offset]) in the loop preheader. */ |
| |
| new_temp = vect_create_addr_base_for_vector_ref (stmt, &new_stmt_list, |
| offset, loop); |
| pe = loop_preheader_edge (loop); |
| if (new_stmt_list) |
| { |
| new_bb = gsi_insert_seq_on_edge_immediate (pe, new_stmt_list); |
| gcc_assert (!new_bb); |
| } |
| |
| *initial_address = new_temp; |
| |
| /* Create: p = (vectype *) initial_base */ |
| vec_stmt = gimple_build_assign (vect_ptr, |
| fold_convert (vect_ptr_type, new_temp)); |
| vect_ptr_init = make_ssa_name (vect_ptr, vec_stmt); |
| gimple_assign_set_lhs (vec_stmt, vect_ptr_init); |
| new_bb = gsi_insert_on_edge_immediate (pe, vec_stmt); |
| gcc_assert (!new_bb); |
| |
| |
| /** (4) Handle the updating of the vector-pointer inside the loop. |
| This is needed when ONLY_INIT is false, and also when AT_LOOP |
| is the inner-loop nested in LOOP (during outer-loop vectorization). |
| **/ |
| |
| if (only_init && at_loop == loop) /* No update in loop is required. */ |
| { |
| /* Copy the points-to information if it exists. */ |
| if (DR_PTR_INFO (dr)) |
| duplicate_ssa_name_ptr_info (vect_ptr_init, DR_PTR_INFO (dr)); |
| vptr = vect_ptr_init; |
| } |
| else |
| { |
| /* The step of the vector pointer is the Vector Size. */ |
| tree step = TYPE_SIZE_UNIT (vectype); |
| /* One exception to the above is when the scalar step of the load in |
| LOOP is zero. In this case the step here is also zero. */ |
| if (*inv_p) |
| step = size_zero_node; |
| |
| standard_iv_increment_position (loop, &incr_gsi, &insert_after); |
| |
| create_iv (vect_ptr_init, |
| fold_convert (vect_ptr_type, step), |
| vect_ptr, loop, &incr_gsi, insert_after, |
| &indx_before_incr, &indx_after_incr); |
| incr = gsi_stmt (incr_gsi); |
| set_vinfo_for_stmt (incr, new_stmt_vec_info (incr, loop_vinfo)); |
| |
| /* Copy the points-to information if it exists. */ |
| if (DR_PTR_INFO (dr)) |
| { |
| duplicate_ssa_name_ptr_info (indx_before_incr, DR_PTR_INFO (dr)); |
| duplicate_ssa_name_ptr_info (indx_after_incr, DR_PTR_INFO (dr)); |
| } |
| merge_alias_info (vect_ptr_init, indx_before_incr); |
| merge_alias_info (vect_ptr_init, indx_after_incr); |
| if (ptr_incr) |
| *ptr_incr = incr; |
| |
| vptr = indx_before_incr; |
| } |
| |
| if (!nested_in_vect_loop || only_init) |
| return vptr; |
| |
| |
| /** (5) Handle the updating of the vector-pointer inside the inner-loop |
| nested in LOOP, if exists: **/ |
| |
| gcc_assert (nested_in_vect_loop); |
| if (!only_init) |
| { |
| standard_iv_increment_position (containing_loop, &incr_gsi, |
| &insert_after); |
| create_iv (vptr, fold_convert (vect_ptr_type, DR_STEP (dr)), vect_ptr, |
| containing_loop, &incr_gsi, insert_after, &indx_before_incr, |
| &indx_after_incr); |
| incr = gsi_stmt (incr_gsi); |
| set_vinfo_for_stmt (incr, new_stmt_vec_info (incr, loop_vinfo)); |
| |
| /* Copy the points-to information if it exists. */ |
| if (DR_PTR_INFO (dr)) |
| { |
| duplicate_ssa_name_ptr_info (indx_before_incr, DR_PTR_INFO (dr)); |
| duplicate_ssa_name_ptr_info (indx_after_incr, DR_PTR_INFO (dr)); |
| } |
| merge_alias_info (vect_ptr_init, indx_before_incr); |
| merge_alias_info (vect_ptr_init, indx_after_incr); |
| if (ptr_incr) |
| *ptr_incr = incr; |
| |
| return indx_before_incr; |
| } |
| else |
| gcc_unreachable (); |
| } |
| |
| |
| /* Function bump_vector_ptr |
| |
| Increment a pointer (to a vector type) by vector-size. If requested, |
| i.e. if PTR-INCR is given, then also connect the new increment stmt |
| to the existing def-use update-chain of the pointer, by modifying |
| the PTR_INCR as illustrated below: |
| |
| The pointer def-use update-chain before this function: |
| DATAREF_PTR = phi (p_0, p_2) |
| .... |
| PTR_INCR: p_2 = DATAREF_PTR + step |
| |
| The pointer def-use update-chain after this function: |
| DATAREF_PTR = phi (p_0, p_2) |
| .... |
| NEW_DATAREF_PTR = DATAREF_PTR + BUMP |
| .... |
| PTR_INCR: p_2 = NEW_DATAREF_PTR + step |
| |
| Input: |
| DATAREF_PTR - ssa_name of a pointer (to vector type) that is being updated |
| in the loop. |
| PTR_INCR - optional. The stmt that updates the pointer in each iteration of |
| the loop. The increment amount across iterations is expected |
| to be vector_size. |
| BSI - location where the new update stmt is to be placed. |
| STMT - the original scalar memory-access stmt that is being vectorized. |
| BUMP - optional. The offset by which to bump the pointer. If not given, |
| the offset is assumed to be vector_size. |
| |
| Output: Return NEW_DATAREF_PTR as illustrated above. |
| |
| */ |
| |
| static tree |
| bump_vector_ptr (tree dataref_ptr, gimple ptr_incr, gimple_stmt_iterator *gsi, |
| gimple stmt, tree bump) |
| { |
| stmt_vec_info stmt_info = vinfo_for_stmt (stmt); |
| struct data_reference *dr = STMT_VINFO_DATA_REF (stmt_info); |
| tree vectype = STMT_VINFO_VECTYPE (stmt_info); |
| tree ptr_var = SSA_NAME_VAR (dataref_ptr); |
| tree update = TYPE_SIZE_UNIT (vectype); |
| gimple incr_stmt; |
| ssa_op_iter iter; |
| use_operand_p use_p; |
| tree new_dataref_ptr; |
| |
| if (bump) |
| update = bump; |
| |
| incr_stmt = gimple_build_assign_with_ops (POINTER_PLUS_EXPR, ptr_var, |
| dataref_ptr, update); |
| new_dataref_ptr = make_ssa_name (ptr_var, incr_stmt); |
| gimple_assign_set_lhs (incr_stmt, new_dataref_ptr); |
| vect_finish_stmt_generation (stmt, incr_stmt, gsi); |
| |
| /* Copy the points-to information if it exists. */ |
| if (DR_PTR_INFO (dr)) |
| duplicate_ssa_name_ptr_info (new_dataref_ptr, DR_PTR_INFO (dr)); |
| merge_alias_info (new_dataref_ptr, dataref_ptr); |
| |
| if (!ptr_incr) |
| return new_dataref_ptr; |
| |
| /* Update the vector-pointer's cross-iteration increment. */ |
| FOR_EACH_SSA_USE_OPERAND (use_p, ptr_incr, iter, SSA_OP_USE) |
| { |
| tree use = USE_FROM_PTR (use_p); |
| |
| if (use == dataref_ptr) |
| SET_USE (use_p, new_dataref_ptr); |
| else |
| gcc_assert (tree_int_cst_compare (use, update) == 0); |
| } |
| |
| return new_dataref_ptr; |
| } |
| |
| |
| /* Function vect_create_destination_var. |
| |
| Create a new temporary of type VECTYPE. */ |
| |
| static tree |
| vect_create_destination_var (tree scalar_dest, tree vectype) |
| { |
| tree vec_dest; |
| const char *new_name; |
| tree type; |
| enum vect_var_kind kind; |
| |
| kind = vectype ? vect_simple_var : vect_scalar_var; |
| type = vectype ? vectype : TREE_TYPE (scalar_dest); |
| |
| gcc_assert (TREE_CODE (scalar_dest) == SSA_NAME); |
| |
| new_name = get_name (scalar_dest); |
| if (!new_name) |
| new_name = "var_"; |
| vec_dest = vect_get_new_vect_var (type, kind, new_name); |
| add_referenced_var (vec_dest); |
| |
| return vec_dest; |
| } |
| |
| |
| /* Function vect_init_vector. |
| |
| Insert a new stmt (INIT_STMT) that initializes a new vector variable with |
| the vector elements of VECTOR_VAR. Place the initialization at BSI if it |
| is not NULL. Otherwise, place the initialization at the loop preheader. |
| Return the DEF of INIT_STMT. |
| It will be used in the vectorization of STMT. */ |
| |
| static tree |
| vect_init_vector (gimple stmt, tree vector_var, tree vector_type, |
| gimple_stmt_iterator *gsi) |
| { |
| stmt_vec_info stmt_vinfo = vinfo_for_stmt (stmt); |
| tree new_var; |
| gimple init_stmt; |
| tree vec_oprnd; |
| edge pe; |
| tree new_temp; |
| basic_block new_bb; |
| |
| new_var = vect_get_new_vect_var (vector_type, vect_simple_var, "cst_"); |
| add_referenced_var (new_var); |
| init_stmt = gimple_build_assign (new_var, vector_var); |
| new_temp = make_ssa_name (new_var, init_stmt); |
| gimple_assign_set_lhs (init_stmt, new_temp); |
| |
| if (gsi) |
| vect_finish_stmt_generation (stmt, init_stmt, gsi); |
| else |
| { |
| loop_vec_info loop_vinfo = STMT_VINFO_LOOP_VINFO (stmt_vinfo); |
| struct loop *loop = LOOP_VINFO_LOOP (loop_vinfo); |
| |
| if (nested_in_vect_loop_p (loop, stmt)) |
| loop = loop->inner; |
| pe = loop_preheader_edge (loop); |
| new_bb = gsi_insert_on_edge_immediate (pe, init_stmt); |
| gcc_assert (!new_bb); |
| } |
| |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| { |
| fprintf (vect_dump, "created new init_stmt: "); |
| print_gimple_stmt (vect_dump, init_stmt, 0, TDF_SLIM); |
| } |
| |
| vec_oprnd = gimple_assign_lhs (init_stmt); |
| return vec_oprnd; |
| } |
| |
| |
| /* For constant and loop invariant defs of SLP_NODE this function returns |
| (vector) defs (VEC_OPRNDS) that will be used in the vectorized stmts. |
| OP_NUM determines if we gather defs for operand 0 or operand 1 of the scalar |
| stmts. NUMBER_OF_VECTORS is the number of vector defs to create. */ |
| |
| static void |
| vect_get_constant_vectors (slp_tree slp_node, VEC(tree,heap) **vec_oprnds, |
| unsigned int op_num, unsigned int number_of_vectors) |
| { |
| VEC (gimple, heap) *stmts = SLP_TREE_SCALAR_STMTS (slp_node); |
| gimple stmt = VEC_index (gimple, stmts, 0); |
| stmt_vec_info stmt_vinfo = vinfo_for_stmt (stmt); |
| tree vectype = STMT_VINFO_VECTYPE (stmt_vinfo); |
| int nunits; |
| tree vec_cst; |
| tree t = NULL_TREE; |
| int j, number_of_places_left_in_vector; |
| tree vector_type; |
| tree op, vop; |
| int group_size = VEC_length (gimple, stmts); |
| unsigned int vec_num, i; |
| int number_of_copies = 1; |
| VEC (tree, heap) *voprnds = VEC_alloc (tree, heap, number_of_vectors); |
| bool constant_p, is_store; |
| |
| if (STMT_VINFO_DATA_REF (stmt_vinfo)) |
| { |
| is_store = true; |
| op = gimple_assign_rhs1 (stmt); |
| } |
| else |
| { |
| is_store = false; |
| op = gimple_op (stmt, op_num + 1); |
| } |
| |
| if (CONSTANT_CLASS_P (op)) |
| { |
| vector_type = vectype; |
| constant_p = true; |
| } |
| else |
| { |
| vector_type = get_vectype_for_scalar_type (TREE_TYPE (op)); |
| gcc_assert (vector_type); |
| constant_p = false; |
| } |
| |
| nunits = TYPE_VECTOR_SUBPARTS (vector_type); |
| |
| /* NUMBER_OF_COPIES is the number of times we need to use the same values in |
| created vectors. It is greater than 1 if unrolling is performed. |
| |
| For example, we have two scalar operands, s1 and s2 (e.g., group of |
| strided accesses of size two), while NUNITS is four (i.e., four scalars |
| of this type can be packed in a vector). The output vector will contain |
| two copies of each scalar operand: {s1, s2, s1, s2}. (NUMBER_OF_COPIES |
| will be 2). |
| |
| If GROUP_SIZE > NUNITS, the scalars will be split into several vectors |
| containing the operands. |
| |
| For example, NUNITS is four as before, and the group size is 8 |
| (s1, s2, ..., s8). We will create two vectors {s1, s2, s3, s4} and |
| {s5, s6, s7, s8}. */ |
| |
| number_of_copies = least_common_multiple (nunits, group_size) / group_size; |
| |
| number_of_places_left_in_vector = nunits; |
| for (j = 0; j < number_of_copies; j++) |
| { |
| for (i = group_size - 1; VEC_iterate (gimple, stmts, i, stmt); i--) |
| { |
| if (is_store) |
| op = gimple_assign_rhs1 (stmt); |
| else |
| op = gimple_op (stmt, op_num + 1); |
| |
| /* Create 'vect_ = {op0,op1,...,opn}'. */ |
| t = tree_cons (NULL_TREE, op, t); |
| |
| number_of_places_left_in_vector--; |
| |
| if (number_of_places_left_in_vector == 0) |
| { |
| number_of_places_left_in_vector = nunits; |
| |
| if (constant_p) |
| vec_cst = build_vector (vector_type, t); |
| else |
| vec_cst = build_constructor_from_list (vector_type, t); |
| VEC_quick_push (tree, voprnds, |
| vect_init_vector (stmt, vec_cst, vector_type, NULL)); |
| t = NULL_TREE; |
| } |
| } |
| } |
| |
| /* Since the vectors are created in the reverse order, we should invert |
| them. */ |
| vec_num = VEC_length (tree, voprnds); |
| for (j = vec_num - 1; j >= 0; j--) |
| { |
| vop = VEC_index (tree, voprnds, j); |
| VEC_quick_push (tree, *vec_oprnds, vop); |
| } |
| |
| VEC_free (tree, heap, voprnds); |
| |
| /* In case that VF is greater than the unrolling factor needed for the SLP |
| group of stmts, NUMBER_OF_VECTORS to be created is greater than |
| NUMBER_OF_SCALARS/NUNITS or NUNITS/NUMBER_OF_SCALARS, and hence we have |
| to replicate the vectors. */ |
| while (number_of_vectors > VEC_length (tree, *vec_oprnds)) |
| { |
| for (i = 0; VEC_iterate (tree, *vec_oprnds, i, vop) && i < vec_num; i++) |
| VEC_quick_push (tree, *vec_oprnds, vop); |
| } |
| } |
| |
| |
| /* Get vectorized definitions from SLP_NODE that contains corresponding |
| vectorized def-stmts. */ |
| |
| static void |
| vect_get_slp_vect_defs (slp_tree slp_node, VEC (tree,heap) **vec_oprnds) |
| { |
| tree vec_oprnd; |
| gimple vec_def_stmt; |
| unsigned int i; |
| |
| gcc_assert (SLP_TREE_VEC_STMTS (slp_node)); |
| |
| for (i = 0; |
| VEC_iterate (gimple, SLP_TREE_VEC_STMTS (slp_node), i, vec_def_stmt); |
| i++) |
| { |
| gcc_assert (vec_def_stmt); |
| vec_oprnd = gimple_get_lhs (vec_def_stmt); |
| VEC_quick_push (tree, *vec_oprnds, vec_oprnd); |
| } |
| } |
| |
| |
| /* Get vectorized definitions for SLP_NODE. |
| If the scalar definitions are loop invariants or constants, collect them and |
| call vect_get_constant_vectors() to create vector stmts. |
| Otherwise, the def-stmts must be already vectorized and the vectorized stmts |
| must be stored in the LEFT/RIGHT node of SLP_NODE, and we call |
| vect_get_slp_vect_defs() to retrieve them. |
| If VEC_OPRNDS1 is NULL, don't get vector defs for the second operand (from |
| the right node. This is used when the second operand must remain scalar. */ |
| |
| static void |
| vect_get_slp_defs (slp_tree slp_node, VEC (tree,heap) **vec_oprnds0, |
| VEC (tree,heap) **vec_oprnds1) |
| { |
| gimple first_stmt; |
| enum tree_code code; |
| int number_of_vects; |
| HOST_WIDE_INT lhs_size_unit, rhs_size_unit; |
| |
| first_stmt = VEC_index (gimple, SLP_TREE_SCALAR_STMTS (slp_node), 0); |
| /* The number of vector defs is determined by the number of vector statements |
| in the node from which we get those statements. */ |
| if (SLP_TREE_LEFT (slp_node)) |
| number_of_vects = SLP_TREE_NUMBER_OF_VEC_STMTS (SLP_TREE_LEFT (slp_node)); |
| else |
| { |
| number_of_vects = SLP_TREE_NUMBER_OF_VEC_STMTS (slp_node); |
| /* Number of vector stmts was calculated according to LHS in |
| vect_schedule_slp_instance(), fix it by replacing LHS with RHS, if |
| necessary. See vect_get_smallest_scalar_type() for details. */ |
| vect_get_smallest_scalar_type (first_stmt, &lhs_size_unit, |
| &rhs_size_unit); |
| if (rhs_size_unit != lhs_size_unit) |
| { |
| number_of_vects *= rhs_size_unit; |
| number_of_vects /= lhs_size_unit; |
| } |
| } |
| |
| /* Allocate memory for vectorized defs. */ |
| *vec_oprnds0 = VEC_alloc (tree, heap, number_of_vects); |
| |
| /* SLP_NODE corresponds either to a group of stores or to a group of |
| unary/binary operations. We don't call this function for loads. */ |
| if (SLP_TREE_LEFT (slp_node)) |
| /* The defs are already vectorized. */ |
| vect_get_slp_vect_defs (SLP_TREE_LEFT (slp_node), vec_oprnds0); |
| else |
| /* Build vectors from scalar defs. */ |
| vect_get_constant_vectors (slp_node, vec_oprnds0, 0, number_of_vects); |
| |
| if (STMT_VINFO_DATA_REF (vinfo_for_stmt (first_stmt))) |
| /* Since we don't call this function with loads, this is a group of |
| stores. */ |
| return; |
| |
| code = gimple_assign_rhs_code (first_stmt); |
| if (get_gimple_rhs_class (code) != GIMPLE_BINARY_RHS || !vec_oprnds1) |
| return; |
| |
| /* The number of vector defs is determined by the number of vector statements |
| in the node from which we get those statements. */ |
| if (SLP_TREE_RIGHT (slp_node)) |
| number_of_vects = SLP_TREE_NUMBER_OF_VEC_STMTS (SLP_TREE_RIGHT (slp_node)); |
| else |
| number_of_vects = SLP_TREE_NUMBER_OF_VEC_STMTS (slp_node); |
| |
| *vec_oprnds1 = VEC_alloc (tree, heap, number_of_vects); |
| |
| if (SLP_TREE_RIGHT (slp_node)) |
| /* The defs are already vectorized. */ |
| vect_get_slp_vect_defs (SLP_TREE_RIGHT (slp_node), vec_oprnds1); |
| else |
| /* Build vectors from scalar defs. */ |
| vect_get_constant_vectors (slp_node, vec_oprnds1, 1, number_of_vects); |
| } |
| |
| |
| /* Function get_initial_def_for_induction |
| |
| Input: |
| STMT - a stmt that performs an induction operation in the loop. |
| IV_PHI - the initial value of the induction variable |
| |
| Output: |
| Return a vector variable, initialized with the first VF values of |
| the induction variable. E.g., for an iv with IV_PHI='X' and |
| evolution S, for a vector of 4 units, we want to return: |
| [X, X + S, X + 2*S, X + 3*S]. */ |
| |
| static tree |
| get_initial_def_for_induction (gimple iv_phi) |
| { |
| stmt_vec_info stmt_vinfo = vinfo_for_stmt (iv_phi); |
| loop_vec_info loop_vinfo = STMT_VINFO_LOOP_VINFO (stmt_vinfo); |
| struct loop *loop = LOOP_VINFO_LOOP (loop_vinfo); |
| tree scalar_type = TREE_TYPE (gimple_phi_result (iv_phi)); |
| tree vectype; |
| int nunits; |
| edge pe = loop_preheader_edge (loop); |
| struct loop *iv_loop; |
| basic_block new_bb; |
| tree vec, vec_init, vec_step, t; |
| tree access_fn; |
| tree new_var; |
| tree new_name; |
| gimple init_stmt, induction_phi, new_stmt; |
| tree induc_def, vec_def, vec_dest; |
| tree init_expr, step_expr; |
| int vf = LOOP_VINFO_VECT_FACTOR (loop_vinfo); |
| int i; |
| bool ok; |
| int ncopies; |
| tree expr; |
| stmt_vec_info phi_info = vinfo_for_stmt (iv_phi); |
| bool nested_in_vect_loop = false; |
| gimple_seq stmts = NULL; |
| imm_use_iterator imm_iter; |
| use_operand_p use_p; |
| gimple exit_phi; |
| edge latch_e; |
| tree loop_arg; |
| gimple_stmt_iterator si; |
| basic_block bb = gimple_bb (iv_phi); |
| |
| vectype = get_vectype_for_scalar_type (scalar_type); |
| gcc_assert (vectype); |
| nunits = TYPE_VECTOR_SUBPARTS (vectype); |
| ncopies = vf / nunits; |
| |
| gcc_assert (phi_info); |
| gcc_assert (ncopies >= 1); |
| |
| /* Find the first insertion point in the BB. */ |
| si = gsi_after_labels (bb); |
| |
| if (INTEGRAL_TYPE_P (scalar_type) || POINTER_TYPE_P (scalar_type)) |
| step_expr = build_int_cst (scalar_type, 0); |
| else |
| step_expr = build_real (scalar_type, dconst0); |
| |
| /* Is phi in an inner-loop, while vectorizing an enclosing outer-loop? */ |
| if (nested_in_vect_loop_p (loop, iv_phi)) |
| { |
| nested_in_vect_loop = true; |
| iv_loop = loop->inner; |
| } |
| else |
| iv_loop = loop; |
| gcc_assert (iv_loop == (gimple_bb (iv_phi))->loop_father); |
| |
| latch_e = loop_latch_edge (iv_loop); |
| loop_arg = PHI_ARG_DEF_FROM_EDGE (iv_phi, latch_e); |
| |
| access_fn = analyze_scalar_evolution (iv_loop, PHI_RESULT (iv_phi)); |
| gcc_assert (access_fn); |
| ok = vect_is_simple_iv_evolution (iv_loop->num, access_fn, |
| &init_expr, &step_expr); |
| gcc_assert (ok); |
| pe = loop_preheader_edge (iv_loop); |
| |
| /* Create the vector that holds the initial_value of the induction. */ |
| if (nested_in_vect_loop) |
| { |
| /* iv_loop is nested in the loop to be vectorized. init_expr had already |
| been created during vectorization of previous stmts; We obtain it from |
| the STMT_VINFO_VEC_STMT of the defining stmt. */ |
| tree iv_def = PHI_ARG_DEF_FROM_EDGE (iv_phi, loop_preheader_edge (iv_loop)); |
| vec_init = vect_get_vec_def_for_operand (iv_def, iv_phi, NULL); |
| } |
| else |
| { |
| /* iv_loop is the loop to be vectorized. Create: |
| vec_init = [X, X+S, X+2*S, X+3*S] (S = step_expr, X = init_expr) */ |
| new_var = vect_get_new_vect_var (scalar_type, vect_scalar_var, "var_"); |
| add_referenced_var (new_var); |
| |
| new_name = force_gimple_operand (init_expr, &stmts, false, new_var); |
| if (stmts) |
| { |
| new_bb = gsi_insert_seq_on_edge_immediate (pe, stmts); |
| gcc_assert (!new_bb); |
| } |
| |
| t = NULL_TREE; |
| t = tree_cons (NULL_TREE, init_expr, t); |
| for (i = 1; i < nunits; i++) |
| { |
| /* Create: new_name_i = new_name + step_expr */ |
| enum tree_code code = POINTER_TYPE_P (scalar_type) |
| ? POINTER_PLUS_EXPR : PLUS_EXPR; |
| init_stmt = gimple_build_assign_with_ops (code, new_var, |
| new_name, step_expr); |
| new_name = make_ssa_name (new_var, init_stmt); |
| gimple_assign_set_lhs (init_stmt, new_name); |
| |
| new_bb = gsi_insert_on_edge_immediate (pe, init_stmt); |
| gcc_assert (!new_bb); |
| |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| { |
| fprintf (vect_dump, "created new init_stmt: "); |
| print_gimple_stmt (vect_dump, init_stmt, 0, TDF_SLIM); |
| } |
| t = tree_cons (NULL_TREE, new_name, t); |
| } |
| /* Create a vector from [new_name_0, new_name_1, ..., new_name_nunits-1] */ |
| vec = build_constructor_from_list (vectype, nreverse (t)); |
| vec_init = vect_init_vector (iv_phi, vec, vectype, NULL); |
| } |
| |
| |
| /* Create the vector that holds the step of the induction. */ |
| if (nested_in_vect_loop) |
| /* iv_loop is nested in the loop to be vectorized. Generate: |
| vec_step = [S, S, S, S] */ |
| new_name = step_expr; |
| else |
| { |
| /* iv_loop is the loop to be vectorized. Generate: |
| vec_step = [VF*S, VF*S, VF*S, VF*S] */ |
| expr = build_int_cst (scalar_type, vf); |
| new_name = fold_build2 (MULT_EXPR, scalar_type, expr, step_expr); |
| } |
| |
| t = NULL_TREE; |
| for (i = 0; i < nunits; i++) |
| t = tree_cons (NULL_TREE, unshare_expr (new_name), t); |
| gcc_assert (CONSTANT_CLASS_P (new_name)); |
| vec = build_vector (vectype, t); |
| vec_step = vect_init_vector (iv_phi, vec, vectype, NULL); |
| |
| |
| /* Create the following def-use cycle: |
| loop prolog: |
| vec_init = ... |
| vec_step = ... |
| loop: |
| vec_iv = PHI <vec_init, vec_loop> |
| ... |
| STMT |
| ... |
| vec_loop = vec_iv + vec_step; */ |
| |
| /* Create the induction-phi that defines the induction-operand. */ |
| vec_dest = vect_get_new_vect_var (vectype, vect_simple_var, "vec_iv_"); |
| add_referenced_var (vec_dest); |
| induction_phi = create_phi_node (vec_dest, iv_loop->header); |
| set_vinfo_for_stmt (induction_phi, |
| new_stmt_vec_info (induction_phi, loop_vinfo)); |
| induc_def = PHI_RESULT (induction_phi); |
| |
| /* Create the iv update inside the loop */ |
| new_stmt = gimple_build_assign_with_ops (PLUS_EXPR, vec_dest, |
| induc_def, vec_step); |
| vec_def = make_ssa_name (vec_dest, new_stmt); |
| gimple_assign_set_lhs (new_stmt, vec_def); |
| gsi_insert_before (&si, new_stmt, GSI_SAME_STMT); |
| set_vinfo_for_stmt (new_stmt, new_stmt_vec_info (new_stmt, loop_vinfo)); |
| |
| /* Set the arguments of the phi node: */ |
| add_phi_arg (induction_phi, vec_init, pe); |
| add_phi_arg (induction_phi, vec_def, loop_latch_edge (iv_loop)); |
| |
| |
| /* In case that vectorization factor (VF) is bigger than the number |
| of elements that we can fit in a vectype (nunits), we have to generate |
| more than one vector stmt - i.e - we need to "unroll" the |
| vector stmt by a factor VF/nunits. For more details see documentation |
| in vectorizable_operation. */ |
| |
| if (ncopies > 1) |
| { |
| stmt_vec_info prev_stmt_vinfo; |
| /* FORNOW. This restriction should be relaxed. */ |
| gcc_assert (!nested_in_vect_loop); |
| |
| /* Create the vector that holds the step of the induction. */ |
| expr = build_int_cst (scalar_type, nunits); |
| new_name = fold_build2 (MULT_EXPR, scalar_type, expr, step_expr); |
| t = NULL_TREE; |
| for (i = 0; i < nunits; i++) |
| t = tree_cons (NULL_TREE, unshare_expr (new_name), t); |
| gcc_assert (CONSTANT_CLASS_P (new_name)); |
| vec = build_vector (vectype, t); |
| vec_step = vect_init_vector (iv_phi, vec, vectype, NULL); |
| |
| vec_def = induc_def; |
| prev_stmt_vinfo = vinfo_for_stmt (induction_phi); |
| for (i = 1; i < ncopies; i++) |
| { |
| /* vec_i = vec_prev + vec_step */ |
| new_stmt = gimple_build_assign_with_ops (PLUS_EXPR, vec_dest, |
| vec_def, vec_step); |
| vec_def = make_ssa_name (vec_dest, new_stmt); |
| gimple_assign_set_lhs (new_stmt, vec_def); |
| |
| gsi_insert_before (&si, new_stmt, GSI_SAME_STMT); |
| set_vinfo_for_stmt (new_stmt, |
| new_stmt_vec_info (new_stmt, loop_vinfo)); |
| STMT_VINFO_RELATED_STMT (prev_stmt_vinfo) = new_stmt; |
| prev_stmt_vinfo = vinfo_for_stmt (new_stmt); |
| } |
| } |
| |
| if (nested_in_vect_loop) |
| { |
| /* Find the loop-closed exit-phi of the induction, and record |
| the final vector of induction results: */ |
| exit_phi = NULL; |
| FOR_EACH_IMM_USE_FAST (use_p, imm_iter, loop_arg) |
| { |
| if (!flow_bb_inside_loop_p (iv_loop, gimple_bb (USE_STMT (use_p)))) |
| { |
| exit_phi = USE_STMT (use_p); |
| break; |
| } |
| } |
| if (exit_phi) |
| { |
| stmt_vec_info stmt_vinfo = vinfo_for_stmt (exit_phi); |
| /* FORNOW. Currently not supporting the case that an inner-loop induction |
| is not used in the outer-loop (i.e. only outside the outer-loop). */ |
| gcc_assert (STMT_VINFO_RELEVANT_P (stmt_vinfo) |
| && !STMT_VINFO_LIVE_P (stmt_vinfo)); |
| |
| STMT_VINFO_VEC_STMT (stmt_vinfo) = new_stmt; |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| { |
| fprintf (vect_dump, "vector of inductions after inner-loop:"); |
| print_gimple_stmt (vect_dump, new_stmt, 0, TDF_SLIM); |
| } |
| } |
| } |
| |
| |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| { |
| fprintf (vect_dump, "transform induction: created def-use cycle: "); |
| print_gimple_stmt (vect_dump, induction_phi, 0, TDF_SLIM); |
| fprintf (vect_dump, "\n"); |
| print_gimple_stmt (vect_dump, SSA_NAME_DEF_STMT (vec_def), 0, TDF_SLIM); |
| } |
| |
| STMT_VINFO_VEC_STMT (phi_info) = induction_phi; |
| return induc_def; |
| } |
| |
| |
| /* Function vect_get_vec_def_for_operand. |
| |
| OP is an operand in STMT. This function returns a (vector) def that will be |
| used in the vectorized stmt for STMT. |
| |
| In the case that OP is an SSA_NAME which is defined in the loop, then |
| STMT_VINFO_VEC_STMT of the defining stmt holds the relevant def. |
| |
| In case OP is an invariant or constant, a new stmt that creates a vector def |
| needs to be introduced. */ |
| |
| static tree |
| vect_get_vec_def_for_operand (tree op, gimple stmt, tree *scalar_def) |
| { |
| tree vec_oprnd; |
| gimple vec_stmt; |
| gimple def_stmt; |
| stmt_vec_info def_stmt_info = NULL; |
| stmt_vec_info stmt_vinfo = vinfo_for_stmt (stmt); |
| tree vectype = STMT_VINFO_VECTYPE (stmt_vinfo); |
| unsigned int nunits = TYPE_VECTOR_SUBPARTS (vectype); |
| loop_vec_info loop_vinfo = STMT_VINFO_LOOP_VINFO (stmt_vinfo); |
| tree vec_inv; |
| tree vec_cst; |
| tree t = NULL_TREE; |
| tree def; |
| int i; |
| enum vect_def_type dt; |
| bool is_simple_use; |
| tree vector_type; |
| |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| { |
| fprintf (vect_dump, "vect_get_vec_def_for_operand: "); |
| print_generic_expr (vect_dump, op, TDF_SLIM); |
| } |
| |
| is_simple_use = vect_is_simple_use (op, loop_vinfo, &def_stmt, &def, &dt); |
| gcc_assert (is_simple_use); |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| { |
| if (def) |
| { |
| fprintf (vect_dump, "def = "); |
| print_generic_expr (vect_dump, def, TDF_SLIM); |
| } |
| if (def_stmt) |
| { |
| fprintf (vect_dump, " def_stmt = "); |
| print_gimple_stmt (vect_dump, def_stmt, 0, TDF_SLIM); |
| } |
| } |
| |
| switch (dt) |
| { |
| /* Case 1: operand is a constant. */ |
| case vect_constant_def: |
| { |
| if (scalar_def) |
| *scalar_def = op; |
| |
| /* Create 'vect_cst_ = {cst,cst,...,cst}' */ |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| fprintf (vect_dump, "Create vector_cst. nunits = %d", nunits); |
| |
| for (i = nunits - 1; i >= 0; --i) |
| { |
| t = tree_cons (NULL_TREE, op, t); |
| } |
| vec_cst = build_vector (vectype, t); |
| return vect_init_vector (stmt, vec_cst, vectype, NULL); |
| } |
| |
| /* Case 2: operand is defined outside the loop - loop invariant. */ |
| case vect_invariant_def: |
| { |
| vector_type = get_vectype_for_scalar_type (TREE_TYPE (def)); |
| gcc_assert (vector_type); |
| nunits = TYPE_VECTOR_SUBPARTS (vector_type); |
| |
| if (scalar_def) |
| *scalar_def = def; |
| |
| /* Create 'vec_inv = {inv,inv,..,inv}' */ |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| fprintf (vect_dump, "Create vector_inv."); |
| |
| for (i = nunits - 1; i >= 0; --i) |
| { |
| t = tree_cons (NULL_TREE, def, t); |
| } |
| |
| /* FIXME: use build_constructor directly. */ |
| vec_inv = build_constructor_from_list (vector_type, t); |
| return vect_init_vector (stmt, vec_inv, vector_type, NULL); |
| } |
| |
| /* Case 3: operand is defined inside the loop. */ |
| case vect_loop_def: |
| { |
| if (scalar_def) |
| *scalar_def = NULL/* FIXME tuples: def_stmt*/; |
| |
| /* Get the def from the vectorized stmt. */ |
| def_stmt_info = vinfo_for_stmt (def_stmt); |
| vec_stmt = STMT_VINFO_VEC_STMT (def_stmt_info); |
| gcc_assert (vec_stmt); |
| if (gimple_code (vec_stmt) == GIMPLE_PHI) |
| vec_oprnd = PHI_RESULT (vec_stmt); |
| else if (is_gimple_call (vec_stmt)) |
| vec_oprnd = gimple_call_lhs (vec_stmt); |
| else |
| vec_oprnd = gimple_assign_lhs (vec_stmt); |
| return vec_oprnd; |
| } |
| |
| /* Case 4: operand is defined by a loop header phi - reduction */ |
| case vect_reduction_def: |
| { |
| struct loop *loop; |
| |
| gcc_assert (gimple_code (def_stmt) == GIMPLE_PHI); |
| loop = (gimple_bb (def_stmt))->loop_father; |
| |
| /* Get the def before the loop */ |
| op = PHI_ARG_DEF_FROM_EDGE (def_stmt, loop_preheader_edge (loop)); |
| return get_initial_def_for_reduction (stmt, op, scalar_def); |
| } |
| |
| /* Case 5: operand is defined by loop-header phi - induction. */ |
| case vect_induction_def: |
| { |
| gcc_assert (gimple_code (def_stmt) == GIMPLE_PHI); |
| |
| /* Get the def from the vectorized stmt. */ |
| def_stmt_info = vinfo_for_stmt (def_stmt); |
| vec_stmt = STMT_VINFO_VEC_STMT (def_stmt_info); |
| gcc_assert (vec_stmt && gimple_code (vec_stmt) == GIMPLE_PHI); |
| vec_oprnd = PHI_RESULT (vec_stmt); |
| return vec_oprnd; |
| } |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| |
| /* Function vect_get_vec_def_for_stmt_copy |
| |
| Return a vector-def for an operand. This function is used when the |
| vectorized stmt to be created (by the caller to this function) is a "copy" |
| created in case the vectorized result cannot fit in one vector, and several |
| copies of the vector-stmt are required. In this case the vector-def is |
| retrieved from the vector stmt recorded in the STMT_VINFO_RELATED_STMT field |
| of the stmt that defines VEC_OPRND. |
| DT is the type of the vector def VEC_OPRND. |
| |
| Context: |
| In case the vectorization factor (VF) is bigger than the number |
| of elements that can fit in a vectype (nunits), we have to generate |
| more than one vector stmt to vectorize the scalar stmt. This situation |
| arises when there are multiple data-types operated upon in the loop; the |
| smallest data-type determines the VF, and as a result, when vectorizing |
| stmts operating on wider types we need to create 'VF/nunits' "copies" of the |
| vector stmt (each computing a vector of 'nunits' results, and together |
| computing 'VF' results in each iteration). This function is called when |
| vectorizing such a stmt (e.g. vectorizing S2 in the illustration below, in |
| which VF=16 and nunits=4, so the number of copies required is 4): |
| |
| scalar stmt: vectorized into: STMT_VINFO_RELATED_STMT |
| |
| S1: x = load VS1.0: vx.0 = memref0 VS1.1 |
| VS1.1: vx.1 = memref1 VS1.2 |
| VS1.2: vx.2 = memref2 VS1.3 |
| VS1.3: vx.3 = memref3 |
| |
| S2: z = x + ... VSnew.0: vz0 = vx.0 + ... VSnew.1 |
| VSnew.1: vz1 = vx.1 + ... VSnew.2 |
| VSnew.2: vz2 = vx.2 + ... VSnew.3 |
| VSnew.3: vz3 = vx.3 + ... |
| |
| The vectorization of S1 is explained in vectorizable_load. |
| The vectorization of S2: |
| To create the first vector-stmt out of the 4 copies - VSnew.0 - |
| the function 'vect_get_vec_def_for_operand' is called to |
| get the relevant vector-def for each operand of S2. For operand x it |
| returns the vector-def 'vx.0'. |
| |
| To create the remaining copies of the vector-stmt (VSnew.j), this |
| function is called to get the relevant vector-def for each operand. It is |
| obtained from the respective VS1.j stmt, which is recorded in the |
| STMT_VINFO_RELATED_STMT field of the stmt that defines VEC_OPRND. |
| |
| For example, to obtain the vector-def 'vx.1' in order to create the |
| vector stmt 'VSnew.1', this function is called with VEC_OPRND='vx.0'. |
| Given 'vx0' we obtain the stmt that defines it ('VS1.0'); from the |
| STMT_VINFO_RELATED_STMT field of 'VS1.0' we obtain the next copy - 'VS1.1', |
| and return its def ('vx.1'). |
| Overall, to create the above sequence this function will be called 3 times: |
| vx.1 = vect_get_vec_def_for_stmt_copy (dt, vx.0); |
| vx.2 = vect_get_vec_def_for_stmt_copy (dt, vx.1); |
| vx.3 = vect_get_vec_def_for_stmt_copy (dt, vx.2); */ |
| |
| static tree |
| vect_get_vec_def_for_stmt_copy (enum vect_def_type dt, tree vec_oprnd) |
| { |
| gimple vec_stmt_for_operand; |
| stmt_vec_info def_stmt_info; |
| |
| /* Do nothing; can reuse same def. */ |
| if (dt == vect_invariant_def || dt == vect_constant_def ) |
| return vec_oprnd; |
| |
| vec_stmt_for_operand = SSA_NAME_DEF_STMT (vec_oprnd); |
| def_stmt_info = vinfo_for_stmt (vec_stmt_for_operand); |
| gcc_assert (def_stmt_info); |
| vec_stmt_for_operand = STMT_VINFO_RELATED_STMT (def_stmt_info); |
| gcc_assert (vec_stmt_for_operand); |
| vec_oprnd = gimple_get_lhs (vec_stmt_for_operand); |
| if (gimple_code (vec_stmt_for_operand) == GIMPLE_PHI) |
| vec_oprnd = PHI_RESULT (vec_stmt_for_operand); |
| else |
| vec_oprnd = gimple_get_lhs (vec_stmt_for_operand); |
| return vec_oprnd; |
| } |
| |
| |
| /* Get vectorized definitions for the operands to create a copy of an original |
| stmt. See vect_get_vec_def_for_stmt_copy() for details. */ |
| |
| static void |
| vect_get_vec_defs_for_stmt_copy (enum vect_def_type *dt, |
| VEC(tree,heap) **vec_oprnds0, |
| VEC(tree,heap) **vec_oprnds1) |
| { |
| tree vec_oprnd = VEC_pop (tree, *vec_oprnds0); |
| |
| vec_oprnd = vect_get_vec_def_for_stmt_copy (dt[0], vec_oprnd); |
| VEC_quick_push (tree, *vec_oprnds0, vec_oprnd); |
| |
| if (vec_oprnds1 && *vec_oprnds1) |
| { |
| vec_oprnd = VEC_pop (tree, *vec_oprnds1); |
| vec_oprnd = vect_get_vec_def_for_stmt_copy (dt[1], vec_oprnd); |
| VEC_quick_push (tree, *vec_oprnds1, vec_oprnd); |
| } |
| } |
| |
| |
| /* Get vectorized definitions for OP0 and OP1, or SLP_NODE if it is not NULL. */ |
| |
| static void |
| vect_get_vec_defs (tree op0, tree op1, gimple stmt, |
| VEC(tree,heap) **vec_oprnds0, VEC(tree,heap) **vec_oprnds1, |
| slp_tree slp_node) |
| { |
| if (slp_node) |
| vect_get_slp_defs (slp_node, vec_oprnds0, vec_oprnds1); |
| else |
| { |
| tree vec_oprnd; |
| |
| *vec_oprnds0 = VEC_alloc (tree, heap, 1); |
| vec_oprnd = vect_get_vec_def_for_operand (op0, stmt, NULL); |
| VEC_quick_push (tree, *vec_oprnds0, vec_oprnd); |
| |
| if (op1) |
| { |
| *vec_oprnds1 = VEC_alloc (tree, heap, 1); |
| vec_oprnd = vect_get_vec_def_for_operand (op1, stmt, NULL); |
| VEC_quick_push (tree, *vec_oprnds1, vec_oprnd); |
| } |
| } |
| } |
| |
| |
| /* Function vect_finish_stmt_generation. |
| |
| Insert a new stmt. */ |
| |
| static void |
| vect_finish_stmt_generation (gimple stmt, gimple vec_stmt, |
| gimple_stmt_iterator *gsi) |
| { |
| stmt_vec_info stmt_info = vinfo_for_stmt (stmt); |
| loop_vec_info loop_vinfo = STMT_VINFO_LOOP_VINFO (stmt_info); |
| |
| gcc_assert (gimple_code (stmt) != GIMPLE_LABEL); |
| |
| gsi_insert_before (gsi, vec_stmt, GSI_SAME_STMT); |
| |
| set_vinfo_for_stmt (vec_stmt, new_stmt_vec_info (vec_stmt, loop_vinfo)); |
| |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| { |
| fprintf (vect_dump, "add new stmt: "); |
| print_gimple_stmt (vect_dump, vec_stmt, 0, TDF_SLIM); |
| } |
| |
| gimple_set_location (vec_stmt, gimple_location (gsi_stmt (*gsi))); |
| } |
| |
| |
| /* Function get_initial_def_for_reduction |
| |
| Input: |
| STMT - a stmt that performs a reduction operation in the loop. |
| INIT_VAL - the initial value of the reduction variable |
| |
| Output: |
| ADJUSTMENT_DEF - a tree that holds a value to be added to the final result |
| of the reduction (used for adjusting the epilog - see below). |
| Return a vector variable, initialized according to the operation that STMT |
| performs. This vector will be used as the initial value of the |
| vector of partial results. |
| |
| Option1 (adjust in epilog): Initialize the vector as follows: |
| add: [0,0,...,0,0] |
| mult: [1,1,...,1,1] |
| min/max: [init_val,init_val,..,init_val,init_val] |
| bit and/or: [init_val,init_val,..,init_val,init_val] |
| and when necessary (e.g. add/mult case) let the caller know |
| that it needs to adjust the result by init_val. |
| |
| Option2: Initialize the vector as follows: |
| add: [0,0,...,0,init_val] |
| mult: [1,1,...,1,init_val] |
| min/max: [init_val,init_val,...,init_val] |
| bit and/or: [init_val,init_val,...,init_val] |
| and no adjustments are needed. |
| |
| For example, for the following code: |
| |
| s = init_val; |
| for (i=0;i<n;i++) |
| s = s + a[i]; |
| |
| STMT is 's = s + a[i]', and the reduction variable is 's'. |
| For a vector of 4 units, we want to return either [0,0,0,init_val], |
| or [0,0,0,0] and let the caller know that it needs to adjust |
| the result at the end by 'init_val'. |
| |
| FORNOW, we are using the 'adjust in epilog' scheme, because this way the |
| initialization vector is simpler (same element in all entries). |
| A cost model should help decide between these two schemes. */ |
| |
| static tree |
| get_initial_def_for_reduction (gimple stmt, tree init_val, tree *adjustment_def) |
| { |
| stmt_vec_info stmt_vinfo = vinfo_for_stmt (stmt); |
| loop_vec_info loop_vinfo = STMT_VINFO_LOOP_VINFO (stmt_vinfo); |
| struct loop *loop = LOOP_VINFO_LOOP (loop_vinfo); |
| tree vectype = STMT_VINFO_VECTYPE (stmt_vinfo); |
| int nunits = TYPE_VECTOR_SUBPARTS (vectype); |
| tree scalar_type = TREE_TYPE (vectype); |
| enum tree_code code = gimple_assign_rhs_code (stmt); |
| tree type = TREE_TYPE (init_val); |
| tree vecdef; |
| tree def_for_init; |
| tree init_def; |
| tree t = NULL_TREE; |
| int i; |
| bool nested_in_vect_loop = false; |
| |
| gcc_assert (POINTER_TYPE_P (type) || INTEGRAL_TYPE_P (type) || SCALAR_FLOAT_TYPE_P (type)); |
| if (nested_in_vect_loop_p (loop, stmt)) |
| nested_in_vect_loop = true; |
| else |
| gcc_assert (loop == (gimple_bb (stmt))->loop_father); |
| |
| vecdef = vect_get_vec_def_for_operand (init_val, stmt, NULL); |
| |
| switch (code) |
| { |
| case WIDEN_SUM_EXPR: |
| case DOT_PROD_EXPR: |
| case PLUS_EXPR: |
| if (nested_in_vect_loop) |
| *adjustment_def = vecdef; |
| else |
| *adjustment_def = init_val; |
| /* Create a vector of zeros for init_def. */ |
| if (SCALAR_FLOAT_TYPE_P (scalar_type)) |
| def_for_init = build_real (scalar_type, dconst0); |
| else |
| def_for_init = build_int_cst (scalar_type, 0); |
| |
| for (i = nunits - 1; i >= 0; --i) |
| t = tree_cons (NULL_TREE, def_for_init, t); |
| init_def = build_vector (vectype, t); |
| break; |
| |
| case MIN_EXPR: |
| case MAX_EXPR: |
| *adjustment_def = NULL_TREE; |
| init_def = vecdef; |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| return init_def; |
| } |
| |
| |
| /* Function vect_create_epilog_for_reduction |
| |
| Create code at the loop-epilog to finalize the result of a reduction |
| computation. |
| |
| VECT_DEF is a vector of partial results. |
| REDUC_CODE is the tree-code for the epilog reduction. |
| NCOPIES is > 1 in case the vectorization factor (VF) is bigger than the |
| number of elements that we can fit in a vectype (nunits). In this case |
| we have to generate more than one vector stmt - i.e - we need to "unroll" |
| the vector stmt by a factor VF/nunits. For more details see documentation |
| in vectorizable_operation. |
| STMT is the scalar reduction stmt that is being vectorized. |
| REDUCTION_PHI is the phi-node that carries the reduction computation. |
| |
| This function: |
| 1. Creates the reduction def-use cycle: sets the arguments for |
| REDUCTION_PHI: |
| The loop-entry argument is the vectorized initial-value of the reduction. |
| The loop-latch argument is VECT_DEF - the vector of partial sums. |
| 2. "Reduces" the vector of partial results VECT_DEF into a single result, |
| by applying the operation specified by REDUC_CODE if available, or by |
| other means (whole-vector shifts or a scalar loop). |
| The function also creates a new phi node at the loop exit to preserve |
| loop-closed form, as illustrated below. |
| |
| The flow at the entry to this function: |
| |
| loop: |
| vec_def = phi <null, null> # REDUCTION_PHI |
| VECT_DEF = vector_stmt # vectorized form of STMT |
| s_loop = scalar_stmt # (scalar) STMT |
| loop_exit: |
| s_out0 = phi <s_loop> # (scalar) EXIT_PHI |
| use <s_out0> |
| use <s_out0> |
| |
| The above is transformed by this function into: |
| |
| loop: |
| vec_def = phi <vec_init, VECT_DEF> # REDUCTION_PHI |
| VECT_DEF = vector_stmt # vectorized form of STMT |
| s_loop = scalar_stmt # (scalar) STMT |
| loop_exit: |
| s_out0 = phi <s_loop> # (scalar) EXIT_PHI |
| v_out1 = phi <VECT_DEF> # NEW_EXIT_PHI |
| v_out2 = reduce <v_out1> |
| s_out3 = extract_field <v_out2, 0> |
| s_out4 = adjust_result <s_out3> |
| use <s_out4> |
| use <s_out4> |
| */ |
| |
| static void |
| vect_create_epilog_for_reduction (tree vect_def, gimple stmt, |
| int ncopies, |
| enum tree_code reduc_code, |
| gimple reduction_phi) |
| { |
| stmt_vec_info stmt_info = vinfo_for_stmt (stmt); |
| stmt_vec_info prev_phi_info; |
| tree vectype; |
| enum machine_mode mode; |
| loop_vec_info loop_vinfo = STMT_VINFO_LOOP_VINFO (stmt_info); |
| struct loop *loop = LOOP_VINFO_LOOP (loop_vinfo); |
| basic_block exit_bb; |
| tree scalar_dest; |
| tree scalar_type; |
| gimple new_phi = NULL, phi; |
| gimple_stmt_iterator exit_gsi; |
| tree vec_dest; |
| tree new_temp = NULL_TREE; |
| tree new_name; |
| gimple epilog_stmt = NULL; |
| tree new_scalar_dest, new_dest; |
| gimple exit_phi; |
| tree bitsize, bitpos, bytesize; |
| enum tree_code code = gimple_assign_rhs_code (stmt); |
| tree adjustment_def; |
| tree vec_initial_def, def; |
| tree orig_name; |
| imm_use_iterator imm_iter; |
| use_operand_p use_p; |
| bool extract_scalar_result = false; |
| tree reduction_op, expr; |
| gimple orig_stmt; |
| gimple use_stmt; |
| bool nested_in_vect_loop = false; |
| VEC(gimple,heap) *phis = NULL; |
| enum vect_def_type dt = vect_unknown_def_type; |
| int j, i; |
| |
| if (nested_in_vect_loop_p (loop, stmt)) |
| { |
| loop = loop->inner; |
| nested_in_vect_loop = true; |
| } |
| |
| switch (get_gimple_rhs_class (gimple_assign_rhs_code (stmt))) |
| { |
| case GIMPLE_SINGLE_RHS: |
| gcc_assert (TREE_OPERAND_LENGTH (gimple_assign_rhs1 (stmt)) == ternary_op); |
| reduction_op = TREE_OPERAND (gimple_assign_rhs1 (stmt), 2); |
| break; |
| case GIMPLE_UNARY_RHS: |
| reduction_op = gimple_assign_rhs1 (stmt); |
| break; |
| case GIMPLE_BINARY_RHS: |
| reduction_op = gimple_assign_rhs2 (stmt); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| |
| vectype = get_vectype_for_scalar_type (TREE_TYPE (reduction_op)); |
| gcc_assert (vectype); |
| mode = TYPE_MODE (vectype); |
| |
| /*** 1. Create the reduction def-use cycle ***/ |
| |
| /* For the case of reduction, vect_get_vec_def_for_operand returns |
| the scalar def before the loop, that defines the initial value |
| of the reduction variable. */ |
| vec_initial_def = vect_get_vec_def_for_operand (reduction_op, stmt, |
| &adjustment_def); |
| |
| phi = reduction_phi; |
| def = vect_def; |
| for (j = 0; j < ncopies; j++) |
| { |
| /* 1.1 set the loop-entry arg of the reduction-phi: */ |
| add_phi_arg (phi, vec_initial_def, loop_preheader_edge (loop)); |
| |
| /* 1.2 set the loop-latch arg for the reduction-phi: */ |
| if (j > 0) |
| def = vect_get_vec_def_for_stmt_copy (dt, def); |
| add_phi_arg (phi, def, loop_latch_edge (loop)); |
| |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| { |
| fprintf (vect_dump, "transform reduction: created def-use cycle: "); |
| print_gimple_stmt (vect_dump, phi, 0, TDF_SLIM); |
| fprintf (vect_dump, "\n"); |
| print_gimple_stmt (vect_dump, SSA_NAME_DEF_STMT (def), 0, TDF_SLIM); |
| } |
| |
| phi = STMT_VINFO_RELATED_STMT (vinfo_for_stmt (phi)); |
| } |
| |
| /*** 2. Create epilog code |
| The reduction epilog code operates across the elements of the vector |
| of partial results computed by the vectorized loop. |
| The reduction epilog code consists of: |
| step 1: compute the scalar result in a vector (v_out2) |
| step 2: extract the scalar result (s_out3) from the vector (v_out2) |
| step 3: adjust the scalar result (s_out3) if needed. |
| |
| Step 1 can be accomplished using one the following three schemes: |
| (scheme 1) using reduc_code, if available. |
| (scheme 2) using whole-vector shifts, if available. |
| (scheme 3) using a scalar loop. In this case steps 1+2 above are |
| combined. |
| |
| The overall epilog code looks like this: |
| |
| s_out0 = phi <s_loop> # original EXIT_PHI |
| v_out1 = phi <VECT_DEF> # NEW_EXIT_PHI |
| v_out2 = reduce <v_out1> # step 1 |
| s_out3 = extract_field <v_out2, 0> # step 2 |
| s_out4 = adjust_result <s_out3> # step 3 |
| |
| (step 3 is optional, and steps 1 and 2 may be combined). |
| Lastly, the uses of s_out0 are replaced by s_out4. |
| |
| ***/ |
| |
| /* 2.1 Create new loop-exit-phi to preserve loop-closed form: |
| v_out1 = phi <v_loop> */ |
| |
| exit_bb = single_exit (loop)->dest; |
| def = vect_def; |
| prev_phi_info = NULL; |
| for (j = 0; j < ncopies; j++) |
| { |
| phi = create_phi_node (SSA_NAME_VAR (vect_def), exit_bb); |
| set_vinfo_for_stmt (phi, new_stmt_vec_info (phi, loop_vinfo)); |
| if (j == 0) |
| new_phi = phi; |
| else |
| { |
| def = vect_get_vec_def_for_stmt_copy (dt, def); |
| STMT_VINFO_RELATED_STMT (prev_phi_info) = phi; |
| } |
| SET_PHI_ARG_DEF (phi, single_exit (loop)->dest_idx, def); |
| prev_phi_info = vinfo_for_stmt (phi); |
| } |
| exit_gsi = gsi_after_labels (exit_bb); |
| |
| /* 2.2 Get the relevant tree-code to use in the epilog for schemes 2,3 |
| (i.e. when reduc_code is not available) and in the final adjustment |
| code (if needed). Also get the original scalar reduction variable as |
| defined in the loop. In case STMT is a "pattern-stmt" (i.e. - it |
| represents a reduction pattern), the tree-code and scalar-def are |
| taken from the original stmt that the pattern-stmt (STMT) replaces. |
| Otherwise (it is a regular reduction) - the tree-code and scalar-def |
| are taken from STMT. */ |
| |
| orig_stmt = STMT_VINFO_RELATED_STMT (stmt_info); |
| if (!orig_stmt) |
| { |
| /* Regular reduction */ |
| orig_stmt = stmt; |
| } |
| else |
| { |
| /* Reduction pattern */ |
| stmt_vec_info stmt_vinfo = vinfo_for_stmt (orig_stmt); |
| gcc_assert (STMT_VINFO_IN_PATTERN_P (stmt_vinfo)); |
| gcc_assert (STMT_VINFO_RELATED_STMT (stmt_vinfo) == stmt); |
| } |
| code = gimple_assign_rhs_code (orig_stmt); |
| scalar_dest = gimple_assign_lhs (orig_stmt); |
| scalar_type = TREE_TYPE (scalar_dest); |
| new_scalar_dest = vect_create_destination_var (scalar_dest, NULL); |
| bitsize = TYPE_SIZE (scalar_type); |
| bytesize = TYPE_SIZE_UNIT (scalar_type); |
| |
| |
| /* In case this is a reduction in an inner-loop while vectorizing an outer |
| loop - we don't need to extract a single scalar result at the end of the |
| inner-loop. The final vector of partial results will be used in the |
| vectorized outer-loop, or reduced to a scalar result at the end of the |
| outer-loop. */ |
| if (nested_in_vect_loop) |
| goto vect_finalize_reduction; |
| |
| /* FORNOW */ |
| gcc_assert (ncopies == 1); |
| |
| /* 2.3 Create the reduction code, using one of the three schemes described |
| above. */ |
| |
| if (reduc_code < NUM_TREE_CODES) |
| { |
| tree tmp; |
| |
| /*** Case 1: Create: |
| v_out2 = reduc_expr <v_out1> */ |
| |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| fprintf (vect_dump, "Reduce using direct vector reduction."); |
| |
| vec_dest = vect_create_destination_var (scalar_dest, vectype); |
| tmp = build1 (reduc_code, vectype, PHI_RESULT (new_phi)); |
| epilog_stmt = gimple_build_assign (vec_dest, tmp); |
| new_temp = make_ssa_name (vec_dest, epilog_stmt); |
| gimple_assign_set_lhs (epilog_stmt, new_temp); |
| gsi_insert_before (&exit_gsi, epilog_stmt, GSI_SAME_STMT); |
| |
| extract_scalar_result = true; |
| } |
| else |
| { |
| enum tree_code shift_code = 0; |
| bool have_whole_vector_shift = true; |
| int bit_offset; |
| int element_bitsize = tree_low_cst (bitsize, 1); |
| int vec_size_in_bits = tree_low_cst (TYPE_SIZE (vectype), 1); |
| tree vec_temp; |
| |
| if (optab_handler (vec_shr_optab, mode)->insn_code != CODE_FOR_nothing) |
| shift_code = VEC_RSHIFT_EXPR; |
| else |
| have_whole_vector_shift = false; |
| |
| /* Regardless of whether we have a whole vector shift, if we're |
| emulating the operation via tree-vect-generic, we don't want |
| to use it. Only the first round of the reduction is likely |
| to still be profitable via emulation. */ |
| /* ??? It might be better to emit a reduction tree code here, so that |
| tree-vect-generic can expand the first round via bit tricks. */ |
| if (!VECTOR_MODE_P (mode)) |
| have_whole_vector_shift = false; |
| else |
| { |
| optab optab = optab_for_tree_code (code, vectype, optab_default); |
| if (optab_handler (optab, mode)->insn_code == CODE_FOR_nothing) |
| have_whole_vector_shift = false; |
| } |
| |
| if (have_whole_vector_shift) |
| { |
| /*** Case 2: Create: |
| for (offset = VS/2; offset >= element_size; offset/=2) |
| { |
| Create: va' = vec_shift <va, offset> |
| Create: va = vop <va, va'> |
| } */ |
| |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| fprintf (vect_dump, "Reduce using vector shifts"); |
| |
| vec_dest = vect_create_destination_var (scalar_dest, vectype); |
| new_temp = PHI_RESULT (new_phi); |
| |
| for (bit_offset = vec_size_in_bits/2; |
| bit_offset >= element_bitsize; |
| bit_offset /= 2) |
| { |
| tree bitpos = size_int (bit_offset); |
| epilog_stmt = gimple_build_assign_with_ops (shift_code, vec_dest, |
| new_temp, bitpos); |
| new_name = make_ssa_name (vec_dest, epilog_stmt); |
| gimple_assign_set_lhs (epilog_stmt, new_name); |
| gsi_insert_before (&exit_gsi, epilog_stmt, GSI_SAME_STMT); |
| |
| epilog_stmt = gimple_build_assign_with_ops (code, vec_dest, |
| new_name, new_temp); |
| new_temp = make_ssa_name (vec_dest, epilog_stmt); |
| gimple_assign_set_lhs (epilog_stmt, new_temp); |
| gsi_insert_before (&exit_gsi, epilog_stmt, GSI_SAME_STMT); |
| } |
| |
| extract_scalar_result = true; |
| } |
| else |
| { |
| tree rhs; |
| |
| /*** Case 3: Create: |
| s = extract_field <v_out2, 0> |
| for (offset = element_size; |
| offset < vector_size; |
| offset += element_size;) |
| { |
| Create: s' = extract_field <v_out2, offset> |
| Create: s = op <s, s'> |
| } */ |
| |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| fprintf (vect_dump, "Reduce using scalar code. "); |
| |
| vec_temp = PHI_RESULT (new_phi); |
| vec_size_in_bits = tree_low_cst (TYPE_SIZE (vectype), 1); |
| rhs = build3 (BIT_FIELD_REF, scalar_type, vec_temp, bitsize, |
| bitsize_zero_node); |
| epilog_stmt = gimple_build_assign (new_scalar_dest, rhs); |
| new_temp = make_ssa_name (new_scalar_dest, epilog_stmt); |
| gimple_assign_set_lhs (epilog_stmt, new_temp); |
| gsi_insert_before (&exit_gsi, epilog_stmt, GSI_SAME_STMT); |
| |
| for (bit_offset = element_bitsize; |
| bit_offset < vec_size_in_bits; |
| bit_offset += element_bitsize) |
| { |
| tree bitpos = bitsize_int (bit_offset); |
| tree rhs = build3 (BIT_FIELD_REF, scalar_type, vec_temp, bitsize, |
| bitpos); |
| |
| epilog_stmt = gimple_build_assign (new_scalar_dest, rhs); |
| new_name = make_ssa_name (new_scalar_dest, epilog_stmt); |
| gimple_assign_set_lhs (epilog_stmt, new_name); |
| gsi_insert_before (&exit_gsi, epilog_stmt, GSI_SAME_STMT); |
| |
| epilog_stmt = gimple_build_assign_with_ops (code, |
| new_scalar_dest, |
| new_name, new_temp); |
| new_temp = make_ssa_name (new_scalar_dest, epilog_stmt); |
| gimple_assign_set_lhs (epilog_stmt, new_temp); |
| gsi_insert_before (&exit_gsi, epilog_stmt, GSI_SAME_STMT); |
| } |
| |
| extract_scalar_result = false; |
| } |
| } |
| |
| /* 2.4 Extract the final scalar result. Create: |
| s_out3 = extract_field <v_out2, bitpos> */ |
| |
| if (extract_scalar_result) |
| { |
| tree rhs; |
| |
| gcc_assert (!nested_in_vect_loop); |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| fprintf (vect_dump, "extract scalar result"); |
| |
| if (BYTES_BIG_ENDIAN) |
| bitpos = size_binop (MULT_EXPR, |
| bitsize_int (TYPE_VECTOR_SUBPARTS (vectype) - 1), |
| TYPE_SIZE (scalar_type)); |
| else |
| bitpos = bitsize_zero_node; |
| |
| rhs = build3 (BIT_FIELD_REF, scalar_type, new_temp, bitsize, bitpos); |
| epilog_stmt = gimple_build_assign (new_scalar_dest, rhs); |
| new_temp = make_ssa_name (new_scalar_dest, epilog_stmt); |
| gimple_assign_set_lhs (epilog_stmt, new_temp); |
| gsi_insert_before (&exit_gsi, epilog_stmt, GSI_SAME_STMT); |
| } |
| |
| vect_finalize_reduction: |
| |
| /* 2.5 Adjust the final result by the initial value of the reduction |
| variable. (When such adjustment is not needed, then |
| 'adjustment_def' is zero). For example, if code is PLUS we create: |
| new_temp = loop_exit_def + adjustment_def */ |
| |
| if (adjustment_def) |
| { |
| if (nested_in_vect_loop) |
| { |
| gcc_assert (TREE_CODE (TREE_TYPE (adjustment_def)) == VECTOR_TYPE); |
| expr = build2 (code, vectype, PHI_RESULT (new_phi), adjustment_def); |
| new_dest = vect_create_destination_var (scalar_dest, vectype); |
| } |
| else |
| { |
| gcc_assert (TREE_CODE (TREE_TYPE (adjustment_def)) != VECTOR_TYPE); |
| expr = build2 (code, scalar_type, new_temp, adjustment_def); |
| new_dest = vect_create_destination_var (scalar_dest, scalar_type); |
| } |
| epilog_stmt = gimple_build_assign (new_dest, expr); |
| new_temp = make_ssa_name (new_dest, epilog_stmt); |
| gimple_assign_set_lhs (epilog_stmt, new_temp); |
| SSA_NAME_DEF_STMT (new_temp) = epilog_stmt; |
| gsi_insert_before (&exit_gsi, epilog_stmt, GSI_SAME_STMT); |
| } |
| |
| |
| /* 2.6 Handle the loop-exit phi */ |
| |
| /* Replace uses of s_out0 with uses of s_out3: |
| Find the loop-closed-use at the loop exit of the original scalar result. |
| (The reduction result is expected to have two immediate uses - one at the |
| latch block, and one at the loop exit). */ |
| phis = VEC_alloc (gimple, heap, 10); |
| FOR_EACH_IMM_USE_FAST (use_p, imm_iter, scalar_dest) |
| { |
| if (!flow_bb_inside_loop_p (loop, gimple_bb (USE_STMT (use_p)))) |
| { |
| exit_phi = USE_STMT (use_p); |
| VEC_quick_push (gimple, phis, exit_phi); |
| } |
| } |
| /* We expect to have found an exit_phi because of loop-closed-ssa form. */ |
| gcc_assert (!VEC_empty (gimple, phis)); |
| |
| for (i = 0; VEC_iterate (gimple, phis, i, exit_phi); i++) |
| { |
| if (nested_in_vect_loop) |
| { |
| stmt_vec_info stmt_vinfo = vinfo_for_stmt (exit_phi); |
| |
| /* FORNOW. Currently not supporting the case that an inner-loop |
| reduction is not used in the outer-loop (but only outside the |
| outer-loop). */ |
| gcc_assert (STMT_VINFO_RELEVANT_P (stmt_vinfo) |
| && !STMT_VINFO_LIVE_P (stmt_vinfo)); |
| |
| epilog_stmt = adjustment_def ? epilog_stmt : new_phi; |
| STMT_VINFO_VEC_STMT (stmt_vinfo) = epilog_stmt; |
| set_vinfo_for_stmt (epilog_stmt, |
| new_stmt_vec_info (epilog_stmt, loop_vinfo)); |
| if (adjustment_def) |
| STMT_VINFO_RELATED_STMT (vinfo_for_stmt (epilog_stmt)) = |
| STMT_VINFO_RELATED_STMT (vinfo_for_stmt (new_phi)); |
| continue; |
| } |
| |
| /* Replace the uses: */ |
| orig_name = PHI_RESULT (exit_phi); |
| FOR_EACH_IMM_USE_STMT (use_stmt, imm_iter, orig_name) |
| FOR_EACH_IMM_USE_ON_STMT (use_p, imm_iter) |
| SET_USE (use_p, new_temp); |
| } |
| VEC_free (gimple, heap, phis); |
| } |
| |
| |
| /* Function vectorizable_reduction. |
| |
| Check if STMT performs a reduction operation that can be vectorized. |
| If VEC_STMT is also passed, vectorize the STMT: create a vectorized |
| stmt to replace it, put it in VEC_STMT, and insert it at BSI. |
| Return FALSE if not a vectorizable STMT, TRUE otherwise. |
| |
| This function also handles reduction idioms (patterns) that have been |
| recognized in advance during vect_pattern_recog. In this case, STMT may be |
| of this form: |
| X = pattern_expr (arg0, arg1, ..., X) |
| and it's STMT_VINFO_RELATED_STMT points to the last stmt in the original |
| sequence that had been detected and replaced by the pattern-stmt (STMT). |
| |
| In some cases of reduction patterns, the type of the reduction variable X is |
| different than the type of the other arguments of STMT. |
| In such cases, the vectype that is used when transforming STMT into a vector |
| stmt is different than the vectype that is used to determine the |
| vectorization factor, because it consists of a different number of elements |
| than the actual number of elements that are being operated upon in parallel. |
| |
| For example, consider an accumulation of shorts into an int accumulator. |
| On some targets it's possible to vectorize this pattern operating on 8 |
| shorts at a time (hence, the vectype for purposes of determining the |
| vectorization factor should be V8HI); on the other hand, the vectype that |
| is used to create the vector form is actually V4SI (the type of the result). |
| |
| Upon entry to this function, STMT_VINFO_VECTYPE records the vectype that |
| indicates what is the actual level of parallelism (V8HI in the example), so |
| that the right vectorization factor would be derived. This vectype |
| corresponds to the type of arguments to the reduction stmt, and should *NOT* |
| be used to create the vectorized stmt. The right vectype for the vectorized |
| stmt is obtained from the type of the result X: |
| get_vectype_for_scalar_type (TREE_TYPE (X)) |
| |
| This means that, contrary to "regular" reductions (or "regular" stmts in |
| general), the following equation: |
| STMT_VINFO_VECTYPE == get_vectype_for_scalar_type (TREE_TYPE (X)) |
| does *NOT* necessarily hold for reduction patterns. */ |
| |
| bool |
| vectorizable_reduction (gimple stmt, gimple_stmt_iterator *gsi, |
| gimple *vec_stmt) |
| { |
| tree vec_dest; |
| tree scalar_dest; |
| tree loop_vec_def0 = NULL_TREE, loop_vec_def1 = NULL_TREE; |
| stmt_vec_info stmt_info = vinfo_for_stmt (stmt); |
| tree vectype = STMT_VINFO_VECTYPE (stmt_info); |
| loop_vec_info loop_vinfo = STMT_VINFO_LOOP_VINFO (stmt_info); |
| struct loop *loop = LOOP_VINFO_LOOP (loop_vinfo); |
| enum tree_code code, orig_code, epilog_reduc_code = 0; |
| enum machine_mode vec_mode; |
| int op_type; |
| optab optab, reduc_optab; |
| tree new_temp = NULL_TREE; |
| tree def; |
| gimple def_stmt; |
| enum vect_def_type dt; |
| gimple new_phi = NULL; |
| tree scalar_type; |
| bool is_simple_use; |
| gimple orig_stmt; |
| stmt_vec_info orig_stmt_info; |
| tree expr = NULL_TREE; |
| int i; |
| int nunits = TYPE_VECTOR_SUBPARTS (vectype); |
| int ncopies = LOOP_VINFO_VECT_FACTOR (loop_vinfo) / nunits; |
| int epilog_copies; |
| stmt_vec_info prev_stmt_info, prev_phi_info; |
| gimple first_phi = NULL; |
| bool single_defuse_cycle = false; |
| tree reduc_def; |
| gimple new_stmt = NULL; |
| int j; |
| tree ops[3]; |
| |
| if (nested_in_vect_loop_p (loop, stmt)) |
| loop = loop->inner; |
| |
| gcc_assert (ncopies >= 1); |
| |
| /* FORNOW: SLP not supported. */ |
| if (STMT_SLP_TYPE (stmt_info)) |
| return false; |
| |
| /* 1. Is vectorizable reduction? */ |
| |
| /* Not supportable if the reduction variable is used in the loop. */ |
| if (STMT_VINFO_RELEVANT (stmt_info) > vect_used_in_outer) |
| return false; |
| |
| /* Reductions that are not used even in an enclosing outer-loop, |
| are expected to be "live" (used out of the loop). */ |
| if (STMT_VINFO_RELEVANT (stmt_info) == vect_unused_in_loop |
| && !STMT_VINFO_LIVE_P (stmt_info)) |
| return false; |
| |
| /* Make sure it was already recognized as a reduction computation. */ |
| if (STMT_VINFO_DEF_TYPE (stmt_info) != vect_reduction_def) |
| return false; |
| |
| /* 2. Has this been recognized as a reduction pattern? |
| |
| Check if STMT represents a pattern that has been recognized |
| in earlier analysis stages. For stmts that represent a pattern, |
| the STMT_VINFO_RELATED_STMT field records the last stmt in |
| the original sequence that constitutes the pattern. */ |
| |
| orig_stmt = STMT_VINFO_RELATED_STMT (stmt_info); |
| if (orig_stmt) |
| { |
| orig_stmt_info = vinfo_for_stmt (orig_stmt); |
| gcc_assert (STMT_VINFO_RELATED_STMT (orig_stmt_info) == stmt); |
| gcc_assert (STMT_VINFO_IN_PATTERN_P (orig_stmt_info)); |
| gcc_assert (!STMT_VINFO_IN_PATTERN_P (stmt_info)); |
| } |
| |
| /* 3. Check the operands of the operation. The first operands are defined |
| inside the loop body. The last operand is the reduction variable, |
| which is defined by the loop-header-phi. */ |
| |
| gcc_assert (is_gimple_assign (stmt)); |
| |
| /* Flatten RHS */ |
| switch (get_gimple_rhs_class (gimple_assign_rhs_code (stmt))) |
| { |
| case GIMPLE_SINGLE_RHS: |
| op_type = TREE_OPERAND_LENGTH (gimple_assign_rhs1 (stmt)); |
| if (op_type == ternary_op) |
| { |
| tree rhs = gimple_assign_rhs1 (stmt); |
| ops[0] = TREE_OPERAND (rhs, 0); |
| ops[1] = TREE_OPERAND (rhs, 1); |
| ops[2] = TREE_OPERAND (rhs, 2); |
| code = TREE_CODE (rhs); |
| } |
| else |
| return false; |
| break; |
| |
| case GIMPLE_BINARY_RHS: |
| code = gimple_assign_rhs_code (stmt); |
| op_type = TREE_CODE_LENGTH (code); |
| gcc_assert (op_type == binary_op); |
| ops[0] = gimple_assign_rhs1 (stmt); |
| ops[1] = gimple_assign_rhs2 (stmt); |
| break; |
| |
| case GIMPLE_UNARY_RHS: |
| return false; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| scalar_dest = gimple_assign_lhs (stmt); |
| scalar_type = TREE_TYPE (scalar_dest); |
| if (!POINTER_TYPE_P (scalar_type) && !INTEGRAL_TYPE_P (scalar_type) |
| && !SCALAR_FLOAT_TYPE_P (scalar_type)) |
| return false; |
| |
| /* All uses but the last are expected to be defined in the loop. |
| The last use is the reduction variable. */ |
| for (i = 0; i < op_type-1; i++) |
| { |
| is_simple_use = vect_is_simple_use (ops[i], loop_vinfo, &def_stmt, |
| &def, &dt); |
| gcc_assert (is_simple_use); |
| if (dt != vect_loop_def |
| && dt != vect_invariant_def |
| && dt != vect_constant_def |
| && dt != vect_induction_def) |
| return false; |
| } |
| |
| is_simple_use = vect_is_simple_use (ops[i], loop_vinfo, &def_stmt, &def, &dt); |
| gcc_assert (is_simple_use); |
| gcc_assert (dt == vect_reduction_def); |
| gcc_assert (gimple_code (def_stmt) == GIMPLE_PHI); |
| if (orig_stmt) |
| gcc_assert (orig_stmt == vect_is_simple_reduction (loop_vinfo, def_stmt)); |
| else |
| gcc_assert (stmt == vect_is_simple_reduction (loop_vinfo, def_stmt)); |
| |
| if (STMT_VINFO_LIVE_P (vinfo_for_stmt (def_stmt))) |
| return false; |
| |
| /* 4. Supportable by target? */ |
| |
| /* 4.1. check support for the operation in the loop */ |
| optab = optab_for_tree_code (code, vectype, optab_default); |
| if (!optab) |
| { |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| fprintf (vect_dump, "no optab."); |
| return false; |
| } |
| vec_mode = TYPE_MODE (vectype); |
| if (optab_handler (optab, vec_mode)->insn_code == CODE_FOR_nothing) |
| { |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| fprintf (vect_dump, "op not supported by target."); |
| if (GET_MODE_SIZE (vec_mode) != UNITS_PER_WORD |
| || LOOP_VINFO_VECT_FACTOR (loop_vinfo) |
| < vect_min_worthwhile_factor (code)) |
| return false; |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| fprintf (vect_dump, "proceeding using word mode."); |
| } |
| |
| /* Worthwhile without SIMD support? */ |
| if (!VECTOR_MODE_P (TYPE_MODE (vectype)) |
| && LOOP_VINFO_VECT_FACTOR (loop_vinfo) |
| < vect_min_worthwhile_factor (code)) |
| { |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| fprintf (vect_dump, "not worthwhile without SIMD support."); |
| return false; |
| } |
| |
| /* 4.2. Check support for the epilog operation. |
| |
| If STMT represents a reduction pattern, then the type of the |
| reduction variable may be different than the type of the rest |
| of the arguments. For example, consider the case of accumulation |
| of shorts into an int accumulator; The original code: |
| S1: int_a = (int) short_a; |
| orig_stmt-> S2: int_acc = plus <int_a ,int_acc>; |
| |
| was replaced with: |
| STMT: int_acc = widen_sum <short_a, int_acc> |
| |
| This means that: |
| 1. The tree-code that is used to create the vector operation in the |
| epilog code (that reduces the partial results) is not the |
| tree-code of STMT, but is rather the tree-code of the original |
| stmt from the pattern that STMT is replacing. I.e, in the example |
| above we want to use 'widen_sum' in the loop, but 'plus' in the |
| epilog. |
| 2. The type (mode) we use to check available target support |
| for the vector operation to be created in the *epilog*, is |
| determined by the type of the reduction variable (in the example |
| above we'd check this: plus_optab[vect_int_mode]). |
| However the type (mode) we use to check available target support |
| for the vector operation to be created *inside the loop*, is |
| determined by the type of the other arguments to STMT (in the |
| example we'd check this: widen_sum_optab[vect_short_mode]). |
| |
| This is contrary to "regular" reductions, in which the types of all |
| the arguments are the same as the type of the reduction variable. |
| For "regular" reductions we can therefore use the same vector type |
| (and also the same tree-code) when generating the epilog code and |
| when generating the code inside the loop. */ |
| |
| if (orig_stmt) |
| { |
| /* This is a reduction pattern: get the vectype from the type of the |
| reduction variable, and get the tree-code from orig_stmt. */ |
| orig_code = gimple_assign_rhs_code (orig_stmt); |
| vectype = get_vectype_for_scalar_type (TREE_TYPE (def)); |
| if (!vectype) |
| { |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| { |
| fprintf (vect_dump, "unsupported data-type "); |
| print_generic_expr (vect_dump, TREE_TYPE (def), TDF_SLIM); |
| } |
| return false; |
| } |
| |
| vec_mode = TYPE_MODE (vectype); |
| } |
| else |
| { |
| /* Regular reduction: use the same vectype and tree-code as used for |
| the vector code inside the loop can be used for the epilog code. */ |
| orig_code = code; |
| } |
| |
| if (!reduction_code_for_scalar_code (orig_code, &epilog_reduc_code)) |
| return false; |
| reduc_optab = optab_for_tree_code (epilog_reduc_code, vectype, optab_default); |
| if (!reduc_optab) |
| { |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| fprintf (vect_dump, "no optab for reduction."); |
| epilog_reduc_code = NUM_TREE_CODES; |
| } |
| if (optab_handler (reduc_optab, vec_mode)->insn_code == CODE_FOR_nothing) |
| { |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| fprintf (vect_dump, "reduc op not supported by target."); |
| epilog_reduc_code = NUM_TREE_CODES; |
| } |
| |
| if (!vec_stmt) /* transformation not required. */ |
| { |
| STMT_VINFO_TYPE (stmt_info) = reduc_vec_info_type; |
| if (!vect_model_reduction_cost (stmt_info, epilog_reduc_code, ncopies)) |
| return false; |
| return true; |
| } |
| |
| /** Transform. **/ |
| |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| fprintf (vect_dump, "transform reduction."); |
| |
| /* Create the destination vector */ |
| vec_dest = vect_create_destination_var (scalar_dest, vectype); |
| |
| /* In case the vectorization factor (VF) is bigger than the number |
| of elements that we can fit in a vectype (nunits), we have to generate |
| more than one vector stmt - i.e - we need to "unroll" the |
| vector stmt by a factor VF/nunits. For more details see documentation |
| in vectorizable_operation. */ |
| |
| /* If the reduction is used in an outer loop we need to generate |
| VF intermediate results, like so (e.g. for ncopies=2): |
| r0 = phi (init, r0) |
| r1 = phi (init, r1) |
| r0 = x0 + r0; |
| r1 = x1 + r1; |
| (i.e. we generate VF results in 2 registers). |
| In this case we have a separate def-use cycle for each copy, and therefore |
| for each copy we get the vector def for the reduction variable from the |
| respective phi node created for this copy. |
| |
| Otherwise (the reduction is unused in the loop nest), we can combine |
| together intermediate results, like so (e.g. for ncopies=2): |
| r = phi (init, r) |
| r = x0 + r; |
| r = x1 + r; |
| (i.e. we generate VF/2 results in a single register). |
| In this case for each copy we get the vector def for the reduction variable |
| from the vectorized reduction operation generated in the previous iteration. |
| */ |
| |
| if (STMT_VINFO_RELEVANT (stmt_info) == vect_unused_in_loop) |
| { |
| single_defuse_cycle = true; |
| epilog_copies = 1; |
| } |
| else |
| epilog_copies = ncopies; |
| |
| prev_stmt_info = NULL; |
| prev_phi_info = NULL; |
| for (j = 0; j < ncopies; j++) |
| { |
| if (j == 0 || !single_defuse_cycle) |
| { |
| /* Create the reduction-phi that defines the reduction-operand. */ |
| new_phi = create_phi_node (vec_dest, loop->header); |
| set_vinfo_for_stmt (new_phi, new_stmt_vec_info (new_phi, loop_vinfo)); |
| } |
| |
| /* Handle uses. */ |
| if (j == 0) |
| { |
| loop_vec_def0 = vect_get_vec_def_for_operand (ops[0], stmt, NULL); |
| if (op_type == ternary_op) |
| { |
| loop_vec_def1 = vect_get_vec_def_for_operand (ops[1], stmt, NULL); |
| } |
| |
| /* Get the vector def for the reduction variable from the phi node */ |
| reduc_def = PHI_RESULT (new_phi); |
| first_phi = new_phi; |
| } |
| else |
| { |
| enum vect_def_type dt = vect_unknown_def_type; /* Dummy */ |
| loop_vec_def0 = vect_get_vec_def_for_stmt_copy (dt, loop_vec_def0); |
| if (op_type == ternary_op) |
| loop_vec_def1 = vect_get_vec_def_for_stmt_copy (dt, loop_vec_def1); |
| |
| if (single_defuse_cycle) |
| reduc_def = gimple_assign_lhs (new_stmt); |
| else |
| reduc_def = PHI_RESULT (new_phi); |
| |
| STMT_VINFO_RELATED_STMT (prev_phi_info) = new_phi; |
| } |
| |
| /* Arguments are ready. create the new vector stmt. */ |
| if (op_type == binary_op) |
| expr = build2 (code, vectype, loop_vec_def0, reduc_def); |
| else |
| expr = build3 (code, vectype, loop_vec_def0, loop_vec_def1, |
| reduc_def); |
| new_stmt = gimple_build_assign (vec_dest, expr); |
| new_temp = make_ssa_name (vec_dest, new_stmt); |
| gimple_assign_set_lhs (new_stmt, new_temp); |
| vect_finish_stmt_generation (stmt, new_stmt, gsi); |
| |
| if (j == 0) |
| STMT_VINFO_VEC_STMT (stmt_info) = *vec_stmt = new_stmt; |
| else |
| STMT_VINFO_RELATED_STMT (prev_stmt_info) = new_stmt; |
| prev_stmt_info = vinfo_for_stmt (new_stmt); |
| prev_phi_info = vinfo_for_stmt (new_phi); |
| } |
| |
| /* Finalize the reduction-phi (set its arguments) and create the |
| epilog reduction code. */ |
| if (!single_defuse_cycle) |
| new_temp = gimple_assign_lhs (*vec_stmt); |
| vect_create_epilog_for_reduction (new_temp, stmt, epilog_copies, |
| epilog_reduc_code, first_phi); |
| return true; |
| } |
| |
| /* Checks if CALL can be vectorized in type VECTYPE. Returns |
| a function declaration if the target has a vectorized version |
| of the function, or NULL_TREE if the function cannot be vectorized. */ |
| |
| tree |
| vectorizable_function (gimple call, tree vectype_out, tree vectype_in) |
| { |
| tree fndecl = gimple_call_fndecl (call); |
| enum built_in_function code; |
| |
| /* We only handle functions that do not read or clobber memory -- i.e. |
| const or novops ones. */ |
| if (!(gimple_call_flags (call) & (ECF_CONST | ECF_NOVOPS))) |
| return NULL_TREE; |
| |
| if (!fndecl |
| || TREE_CODE (fndecl) != FUNCTION_DECL |
| || !DECL_BUILT_IN (fndecl)) |
| return NULL_TREE; |
| |
| code = DECL_FUNCTION_CODE (fndecl); |
| return targetm.vectorize.builtin_vectorized_function (code, vectype_out, |
| vectype_in); |
| } |
| |
| /* Function vectorizable_call. |
| |
| Check if STMT performs a function call that can be vectorized. |
| If VEC_STMT is also passed, vectorize the STMT: create a vectorized |
| stmt to replace it, put it in VEC_STMT, and insert it at BSI. |
| Return FALSE if not a vectorizable STMT, TRUE otherwise. */ |
| |
| bool |
| vectorizable_call (gimple stmt, gimple_stmt_iterator *gsi, gimple *vec_stmt) |
| { |
| tree vec_dest; |
| tree scalar_dest; |
| tree op, type; |
| tree vec_oprnd0 = NULL_TREE, vec_oprnd1 = NULL_TREE; |
| stmt_vec_info stmt_info = vinfo_for_stmt (stmt), prev_stmt_info; |
| tree vectype_out, vectype_in; |
| int nunits_in; |
| int nunits_out; |
| loop_vec_info loop_vinfo = STMT_VINFO_LOOP_VINFO (stmt_info); |
| tree fndecl, new_temp, def, rhs_type, lhs_type; |
| gimple def_stmt; |
| enum vect_def_type dt[2] = {vect_unknown_def_type, vect_unknown_def_type}; |
| gimple new_stmt; |
| int ncopies, j; |
| VEC(tree, heap) *vargs = NULL; |
| enum { NARROW, NONE, WIDEN } modifier; |
| size_t i, nargs; |
| |
| if (!STMT_VINFO_RELEVANT_P (stmt_info)) |
| return false; |
| |
| if (STMT_VINFO_DEF_TYPE (stmt_info) != vect_loop_def) |
| return false; |
| |
| /* FORNOW: SLP not supported. */ |
| if (STMT_SLP_TYPE (stmt_info)) |
| return false; |
| |
| /* Is STMT a vectorizable call? */ |
| if (!is_gimple_call (stmt)) |
| return false; |
| |
| if (TREE_CODE (gimple_call_lhs (stmt)) != SSA_NAME) |
| return false; |
| |
| /* Process function arguments. */ |
| rhs_type = NULL_TREE; |
| nargs = gimple_call_num_args (stmt); |
| |
| /* Bail out if the function has more than two arguments, we |
| do not have interesting builtin functions to vectorize with |
| more than two arguments. No arguments is also not good. */ |
| if (nargs == 0 || nargs > 2) |
| return false; |
| |
| for (i = 0; i < nargs; i++) |
| { |
| op = gimple_call_arg (stmt, i); |
| |
| /* We can only handle calls with arguments of the same type. */ |
| if (rhs_type |
| && rhs_type != TREE_TYPE (op)) |
| { |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| fprintf (vect_dump, "argument types differ."); |
| return false; |
| } |
| rhs_type = TREE_TYPE (op); |
| |
| if (!vect_is_simple_use (op, loop_vinfo, &def_stmt, &def, &dt[i])) |
| { |
| if (vect_print_dump_info (REPORT_DETAILS)) |
| fprintf (vect_dump, "use not simple."); |
| return false; |
| } |
| } |
| |
| vectype_in = get_vectype_for_scalar_type (rhs_type); |
| if (!vectype_in) |
| return false; |
| nunits_in = TYPE_VECTOR_SUBPARTS (vectype_in); |
| |
| lhs_type = TREE_TYPE (gimple_call_lhs (stmt)); |
| vectype_out = get_vectype_for_scalar_type (lhs_type); |
| if (!vectype_out) |
| return false; |
|