/* Schedule GIMPLE vector statements. Copyright (C) 2020-2024 Free Software Foundation, Inc. This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ #include "config.h" #include "system.h" #include "coretypes.h" #include "backend.h" #include "rtl.h" #include "tree.h" #include "gimple.h" #include "tree-pass.h" #include "ssa.h" #include "expmed.h" #include "optabs-tree.h" #include "tree-eh.h" #include "gimple-iterator.h" #include "gimplify-me.h" #include "gimplify.h" #include "tree-cfg.h" #include "bitmap.h" #include "tree-ssa-dce.h" #include "memmodel.h" #include "optabs.h" #include "gimple-fold.h" #include "internal-fn.h" /* Expand all ARRAY_REF(VIEW_CONVERT_EXPR) gimple assignments into calls to internal function based on vector type of selected expansion. For vec_set: VIEW_CONVERT_EXPR(u)[_1] = i_4(D); => _7 = u; _8 = .VEC_SET (_7, i_4(D), _1); u = _8; For vec_extract: _3 = VIEW_CONVERT_EXPR(vD.2208)[idx_2(D)]; => _4 = vD.2208; _3 = .VEC_EXTRACT (_4, idx_2(D)); */ static bool gimple_expand_vec_set_extract_expr (struct function *fun, gimple_stmt_iterator *gsi) { gcall *new_stmt = NULL; gassign *ass_stmt = NULL; bool cfg_changed = false; /* Only consider code == GIMPLE_ASSIGN. */ gassign *stmt = dyn_cast (gsi_stmt (*gsi)); if (!stmt) return false; bool is_extract = false; tree lhs = gimple_assign_lhs (stmt); tree rhs = gimple_assign_rhs1 (stmt); tree val, ref; if (TREE_CODE (lhs) == ARRAY_REF) { /* Assume it is a vec_set. */ val = rhs; ref = lhs; } else if (TREE_CODE (rhs) == ARRAY_REF) { /* vec_extract. */ is_extract = true; val = lhs; ref = rhs; } else return false; tree op0 = TREE_OPERAND (ref, 0); if (TREE_CODE (op0) == VIEW_CONVERT_EXPR && DECL_P (TREE_OPERAND (op0, 0)) && VECTOR_TYPE_P (TREE_TYPE (TREE_OPERAND (op0, 0))) && TYPE_MODE (TREE_TYPE (ref)) == TYPE_MODE (TREE_TYPE (TREE_TYPE (TREE_OPERAND (op0, 0))))) { tree pos = TREE_OPERAND (ref, 1); tree view_op0 = TREE_OPERAND (op0, 0); machine_mode outermode = TYPE_MODE (TREE_TYPE (view_op0)); machine_mode extract_mode = TYPE_MODE (TREE_TYPE (ref)); if ((auto_var_in_fn_p (view_op0, fun->decl) || (VAR_P (view_op0) && DECL_HARD_REGISTER (view_op0))) && !TREE_ADDRESSABLE (view_op0) && ((!is_extract && can_vec_set_var_idx_p (outermode)) || (is_extract && can_vec_extract_var_idx_p (outermode, extract_mode)))) { location_t loc = gimple_location (stmt); tree var_src = make_ssa_name (TREE_TYPE (view_op0)); ass_stmt = gimple_build_assign (var_src, view_op0); gimple_set_vuse (ass_stmt, gimple_vuse (stmt)); gimple_set_location (ass_stmt, loc); gsi_insert_before (gsi, ass_stmt, GSI_SAME_STMT); if (!is_extract) { tree var_dst = make_ssa_name (TREE_TYPE (view_op0)); new_stmt = gimple_build_call_internal (IFN_VEC_SET, 3, var_src, val, pos); gimple_call_set_lhs (new_stmt, var_dst); gimple_set_location (new_stmt, loc); gsi_insert_before (gsi, new_stmt, GSI_SAME_STMT); ass_stmt = gimple_build_assign (view_op0, var_dst); gimple_set_location (ass_stmt, loc); gimple_move_vops (ass_stmt, stmt); gsi_insert_before (gsi, ass_stmt, GSI_SAME_STMT); basic_block bb = gimple_bb (stmt); if (gsi_remove (gsi, true) && gimple_purge_dead_eh_edges (bb)) cfg_changed = true; *gsi = gsi_for_stmt (ass_stmt); } else { new_stmt = gimple_build_call_internal (IFN_VEC_EXTRACT, 2, var_src, pos); gimple_call_set_lhs (new_stmt, lhs); gsi_replace (gsi, new_stmt, true); cfg_changed = true; } } } return cfg_changed; } /* Expand all VEC_COND_EXPR gimple assignments into calls to internal function based on type of selected expansion. */ static gimple * gimple_expand_vec_cond_expr (struct function *fun, gimple_stmt_iterator *gsi, hash_map *vec_cond_ssa_name_uses) { tree lhs, op0a = NULL_TREE, op0b = NULL_TREE; enum tree_code code; enum tree_code tcode; machine_mode cmp_op_mode; bool unsignedp; enum insn_code icode; imm_use_iterator imm_iter; /* Only consider code == GIMPLE_ASSIGN. */ gassign *stmt = dyn_cast (gsi_stmt (*gsi)); if (!stmt) return NULL; code = gimple_assign_rhs_code (stmt); if (code != VEC_COND_EXPR) return NULL; tree op0 = gimple_assign_rhs1 (stmt); tree op1 = gimple_assign_rhs2 (stmt); tree op2 = gimple_assign_rhs3 (stmt); lhs = gimple_assign_lhs (stmt); machine_mode mode = TYPE_MODE (TREE_TYPE (lhs)); /* Lower mask typed, non-vector mode VEC_COND_EXPRs to bitwise operations. Those can end up generated by folding and at least for integer mode masks we cannot expect vcond expanders to exist. We lower a ? b : c to (b & a) | (c & ~a). */ if (VECTOR_BOOLEAN_TYPE_P (TREE_TYPE (lhs)) && !VECTOR_MODE_P (mode)) { gcc_assert (types_compatible_p (TREE_TYPE (op0), TREE_TYPE (op1))); gimple_seq stmts = NULL; tree type = TREE_TYPE (lhs); location_t loc = gimple_location (stmt); tree tem0 = gimple_build (&stmts, loc, BIT_AND_EXPR, type, op1, op0); tree tem1 = gimple_build (&stmts, loc, BIT_NOT_EXPR, type, op0); tree tem2 = gimple_build (&stmts, loc, BIT_AND_EXPR, type, op2, tem1); tree tem3 = gimple_build (&stmts, loc, BIT_IOR_EXPR, type, tem0, tem2); gsi_insert_seq_before (gsi, stmts, GSI_SAME_STMT); return gimple_build_assign (lhs, tem3); } bool can_compute_op0 = true; gcc_assert (!COMPARISON_CLASS_P (op0)); if (TREE_CODE (op0) == SSA_NAME) { unsigned int used_vec_cond_exprs = 0; unsigned int *slot = vec_cond_ssa_name_uses->get (op0); if (slot) used_vec_cond_exprs = *slot; else { gimple *use_stmt; FOR_EACH_IMM_USE_STMT (use_stmt, imm_iter, op0) { gassign *assign = dyn_cast (use_stmt); if (assign != NULL && gimple_assign_rhs_code (assign) == VEC_COND_EXPR && gimple_assign_rhs1 (assign) == op0) used_vec_cond_exprs++; } vec_cond_ssa_name_uses->put (op0, used_vec_cond_exprs); } gassign *def_stmt = dyn_cast (SSA_NAME_DEF_STMT (op0)); if (def_stmt) { tcode = gimple_assign_rhs_code (def_stmt); op0a = gimple_assign_rhs1 (def_stmt); op0b = gimple_assign_rhs2 (def_stmt); tree op0_type = TREE_TYPE (op0); tree op0a_type = TREE_TYPE (op0a); if (TREE_CODE_CLASS (tcode) == tcc_comparison) can_compute_op0 = expand_vec_cmp_expr_p (op0a_type, op0_type, tcode); /* Try to fold x CMP y ? -1 : 0 to x CMP y. */ if (can_compute_op0 && integer_minus_onep (op1) && integer_zerop (op2) && TYPE_MODE (TREE_TYPE (lhs)) == TYPE_MODE (TREE_TYPE (op0))) { tree conv_op = build1 (VIEW_CONVERT_EXPR, TREE_TYPE (lhs), op0); gassign *new_stmt = gimple_build_assign (lhs, conv_op); gsi_replace (gsi, new_stmt, true); return new_stmt; } /* When the compare has EH we do not want to forward it when it has multiple uses and in general because of the complication with EH redirection. */ if (stmt_can_throw_internal (fun, def_stmt)) tcode = TREE_CODE (op0); /* If we can compute op0 and have multiple uses, keep the SSA name and use vcond_mask. */ else if (can_compute_op0 && used_vec_cond_exprs >= 2 && (get_vcond_mask_icode (mode, TYPE_MODE (op0_type)) != CODE_FOR_nothing)) tcode = TREE_CODE (op0); } else tcode = TREE_CODE (op0); } else tcode = TREE_CODE (op0); if (TREE_CODE_CLASS (tcode) != tcc_comparison) { gcc_assert (VECTOR_BOOLEAN_TYPE_P (TREE_TYPE (op0))); if (get_vcond_mask_icode (mode, TYPE_MODE (TREE_TYPE (op0))) != CODE_FOR_nothing) return gimple_build_call_internal (IFN_VCOND_MASK, 3, op0, op1, op2); /* Fake op0 < 0. */ else { gcc_assert (GET_MODE_CLASS (TYPE_MODE (TREE_TYPE (op0))) == MODE_VECTOR_INT); op0a = op0; op0b = build_zero_cst (TREE_TYPE (op0)); tcode = LT_EXPR; } } cmp_op_mode = TYPE_MODE (TREE_TYPE (op0a)); unsignedp = TYPE_UNSIGNED (TREE_TYPE (op0a)); gcc_assert (known_eq (GET_MODE_NUNITS (mode), GET_MODE_NUNITS (cmp_op_mode))); icode = get_vcond_icode (mode, cmp_op_mode, unsignedp); /* Some targets do not have vcondeq and only vcond with NE/EQ but not vcondu, so make sure to also try vcond here as vcond_icode_p would canonicalize the optab query to. */ if (icode == CODE_FOR_nothing && (tcode == NE_EXPR || tcode == EQ_EXPR) && ((icode = get_vcond_icode (mode, cmp_op_mode, !unsignedp)) != CODE_FOR_nothing)) unsignedp = !unsignedp; if (icode == CODE_FOR_nothing) { if (tcode == LT_EXPR && op0a == op0) { /* A VEC_COND_EXPR condition could be folded from EQ_EXPR/NE_EXPR into a constant when only get_vcond_eq_icode is supported. Try changing it to NE_EXPR. */ tcode = NE_EXPR; } if ((tcode == EQ_EXPR || tcode == NE_EXPR) && direct_internal_fn_supported_p (IFN_VCONDEQ, TREE_TYPE (lhs), TREE_TYPE (op0a), OPTIMIZE_FOR_BOTH)) { tree tcode_tree = build_int_cst (integer_type_node, tcode); return gimple_build_call_internal (IFN_VCONDEQ, 5, op0a, op0b, op1, op2, tcode_tree); } gcc_assert (VECTOR_BOOLEAN_TYPE_P (TREE_TYPE (op0)) && can_compute_op0 && (get_vcond_mask_icode (mode, TYPE_MODE (TREE_TYPE (op0))) != CODE_FOR_nothing)); return gimple_build_call_internal (IFN_VCOND_MASK, 3, op0, op1, op2); } tree tcode_tree = build_int_cst (integer_type_node, tcode); return gimple_build_call_internal (unsignedp ? IFN_VCONDU : IFN_VCOND, 5, op0a, op0b, op1, op2, tcode_tree); } namespace { const pass_data pass_data_gimple_isel = { GIMPLE_PASS, /* type */ "isel", /* name */ OPTGROUP_VEC, /* optinfo_flags */ TV_NONE, /* tv_id */ PROP_cfg, /* properties_required */ 0, /* properties_provided */ 0, /* properties_destroyed */ 0, /* todo_flags_start */ TODO_update_ssa, /* todo_flags_finish */ }; class pass_gimple_isel : public gimple_opt_pass { public: pass_gimple_isel (gcc::context *ctxt) : gimple_opt_pass (pass_data_gimple_isel, ctxt) {} /* opt_pass methods: */ bool gate (function *) final override { return true; } unsigned int execute (function *fun) final override; }; // class pass_gimple_isel /* Iterate all gimple statements and perform pre RTL expansion GIMPLE massaging to improve instruction selection. */ unsigned int pass_gimple_isel::execute (struct function *fun) { gimple_stmt_iterator gsi; basic_block bb; hash_map vec_cond_ssa_name_uses; auto_bitmap dce_ssa_names; bool cfg_changed = false; FOR_EACH_BB_FN (bb, fun) { for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) { /* Pre-expand VEC_COND_EXPRs to .VCOND* internal function calls mapping to supported optabs. */ gimple *g = gimple_expand_vec_cond_expr (fun, &gsi, &vec_cond_ssa_name_uses); if (g != NULL) { tree lhs = gimple_assign_lhs (gsi_stmt (gsi)); gimple_set_lhs (g, lhs); gsi_replace (&gsi, g, false); } /* Recognize .VEC_SET and .VEC_EXTRACT patterns. */ cfg_changed |= gimple_expand_vec_set_extract_expr (fun, &gsi); if (gsi_end_p (gsi)) break; gassign *stmt = dyn_cast (*gsi); if (!stmt) continue; tree_code code = gimple_assign_rhs_code (stmt); tree lhs = gimple_assign_lhs (stmt); if (TREE_CODE_CLASS (code) == tcc_comparison && !has_single_use (lhs)) { /* Duplicate COND_EXPR condition defs when they are comparisons so RTL expansion with the help of TER can perform better if conversion. */ imm_use_iterator imm_iter; use_operand_p use_p; auto_vec cond_exprs; unsigned cnt = 0; FOR_EACH_IMM_USE_FAST (use_p, imm_iter, lhs) { if (is_gimple_debug (USE_STMT (use_p))) continue; cnt++; if (gimple_bb (USE_STMT (use_p)) == bb && is_gimple_assign (USE_STMT (use_p)) && gimple_assign_rhs1_ptr (USE_STMT (use_p)) == use_p->use && gimple_assign_rhs_code (USE_STMT (use_p)) == COND_EXPR) cond_exprs.safe_push (as_a (USE_STMT (use_p))); } for (unsigned i = cond_exprs.length () == cnt ? 1 : 0; i < cond_exprs.length (); ++i) { gassign *copy = as_a (gimple_copy (stmt)); tree new_def = duplicate_ssa_name (lhs, copy); gimple_assign_set_lhs (copy, new_def); auto gsi2 = gsi_for_stmt (cond_exprs[i]); gsi_insert_before (&gsi2, copy, GSI_SAME_STMT); gimple_assign_set_rhs1 (cond_exprs[i], new_def); update_stmt (cond_exprs[i]); } } } } for (auto it = vec_cond_ssa_name_uses.begin (); it != vec_cond_ssa_name_uses.end (); ++it) bitmap_set_bit (dce_ssa_names, SSA_NAME_VERSION ((*it).first)); simple_dce_from_worklist (dce_ssa_names); return cfg_changed ? TODO_cleanup_cfg : 0; } } // anon namespace gimple_opt_pass * make_pass_gimple_isel (gcc::context *ctxt) { return new pass_gimple_isel (ctxt); }