| /* relax-opt pass of Andes NDS32 cpu for GNU compiler |
| Copyright (C) 2012-2022 Free Software Foundation, Inc. |
| Contributed by Andes Technology Corporation. |
| |
| 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/>. */ |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| #define IN_TARGET_CODE 1 |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "target.h" |
| #include "rtl.h" |
| #include "tree.h" |
| #include "stringpool.h" |
| #include "attribs.h" |
| #include "df.h" |
| #include "memmodel.h" |
| #include "tm_p.h" |
| #include "optabs.h" /* For GEN_FCN. */ |
| #include "regs.h" |
| #include "emit-rtl.h" |
| #include "recog.h" |
| #include "diagnostic-core.h" |
| #include "stor-layout.h" |
| #include "varasm.h" |
| #include "calls.h" |
| #include "output.h" |
| #include "explow.h" |
| #include "expr.h" |
| #include "tm-constrs.h" |
| #include "builtins.h" |
| #include "cpplib.h" |
| #include "insn-attr.h" |
| #include "cfgrtl.h" |
| #include "tree-pass.h" |
| |
| using namespace nds32; |
| |
| /* This is used to create unique relax hint id value. |
| The initial value is 0. */ |
| static int relax_group_id = 0; |
| |
| /* Group the following pattern as relax candidates: |
| |
| 1. sethi $ra, hi20(sym) |
| ori $ra, $ra, lo12(sym) |
| ==> |
| addi.gp $ra, sym |
| |
| 2. sethi $ra, hi20(sym) |
| lwi $rb, [$ra + lo12(sym)] |
| ==> |
| lwi.gp $rb, [(sym)] |
| |
| 3. sethi $ra, hi20(sym) |
| ori $ra, $ra, lo12(sym) |
| lwi $rb, [$ra] |
| swi $rc, [$ra] |
| ==> |
| lwi37 $rb, [(sym)] |
| swi37 $rc, [(sym)] */ |
| |
| int |
| nds32_alloc_relax_group_id () |
| { |
| return relax_group_id++; |
| } |
| |
| /* Return true if is load/store with REG addressing mode |
| and memory mode is SImode. */ |
| static bool |
| nds32_reg_base_load_store_p (rtx_insn *insn) |
| { |
| rtx mem_src = NULL_RTX; |
| |
| switch (get_attr_type (insn)) |
| { |
| case TYPE_LOAD: |
| mem_src = SET_SRC (PATTERN (insn)); |
| break; |
| case TYPE_STORE: |
| mem_src = SET_DEST (PATTERN (insn)); |
| break; |
| default: |
| break; |
| } |
| |
| /* Find load/store insn with addressing mode is REG. */ |
| if (mem_src != NULL_RTX) |
| { |
| if ((GET_CODE (mem_src) == ZERO_EXTEND) |
| || (GET_CODE (mem_src) == SIGN_EXTEND)) |
| mem_src = XEXP (mem_src, 0); |
| |
| if (GET_CODE (XEXP (mem_src, 0)) == REG) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Return true if insn is a sp/fp base or sp/fp plus load-store instruction. */ |
| |
| static bool |
| nds32_sp_base_or_plus_load_store_p (rtx_insn *insn) |
| { |
| rtx mem_src = NULL_RTX; |
| |
| switch (get_attr_type (insn)) |
| { |
| case TYPE_LOAD: |
| mem_src = SET_SRC (PATTERN (insn)); |
| break; |
| case TYPE_STORE: |
| mem_src = SET_DEST (PATTERN (insn)); |
| break; |
| default: |
| break; |
| } |
| /* Find load/store insn with addressing mode is REG. */ |
| if (mem_src != NULL_RTX) |
| { |
| if ((GET_CODE (mem_src) == ZERO_EXTEND) |
| || (GET_CODE (mem_src) == SIGN_EXTEND)) |
| mem_src = XEXP (mem_src, 0); |
| |
| if ((GET_CODE (XEXP (mem_src, 0)) == PLUS)) |
| mem_src = XEXP (mem_src, 0); |
| |
| if (REG_P (XEXP (mem_src, 0)) |
| && ((frame_pointer_needed |
| && REGNO (XEXP (mem_src, 0)) == FP_REGNUM) |
| || REGNO (XEXP (mem_src, 0)) == SP_REGNUM)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Return true if is load with [REG + REG/CONST_INT] addressing mode. */ |
| static bool |
| nds32_plus_reg_load_store_p (rtx_insn *insn) |
| { |
| rtx mem_src = NULL_RTX; |
| |
| switch (get_attr_type (insn)) |
| { |
| case TYPE_LOAD: |
| mem_src = SET_SRC (PATTERN (insn)); |
| break; |
| case TYPE_STORE: |
| mem_src = SET_DEST (PATTERN (insn)); |
| break; |
| default: |
| break; |
| } |
| |
| /* Find load/store insn with addressing mode is [REG + REG/CONST]. */ |
| if (mem_src != NULL_RTX) |
| { |
| if ((GET_CODE (mem_src) == ZERO_EXTEND) |
| || (GET_CODE (mem_src) == SIGN_EXTEND)) |
| mem_src = XEXP (mem_src, 0); |
| |
| if ((GET_CODE (XEXP (mem_src, 0)) == PLUS)) |
| mem_src = XEXP (mem_src, 0); |
| else |
| return false; |
| |
| if (GET_CODE (XEXP (mem_src, 0)) == REG) |
| return true; |
| |
| } |
| |
| return false; |
| } |
| |
| /* Return true if x is const and the referance is ict symbol. */ |
| static bool |
| nds32_ict_const_p (rtx x) |
| { |
| if (GET_CODE (x) == CONST) |
| { |
| x = XEXP (x, 0); |
| return nds32_indirect_call_referenced_p (x); |
| } |
| return FALSE; |
| } |
| |
| /* Group the following pattern as relax candidates: |
| |
| GOT: |
| sethi $ra, hi20(sym) |
| ori $ra, $ra, lo12(sym) |
| lw $rb, [$ra + $gp] |
| |
| GOTOFF, TLSLE: |
| sethi $ra, hi20(sym) |
| ori $ra, $ra, lo12(sym) |
| LS $rb, [$ra + $gp] |
| |
| GOTOFF, TLSLE: |
| sethi $ra, hi20(sym) |
| ori $ra, $ra, lo12(sym) |
| add $rb, $ra, $gp($tp) |
| |
| Initial GOT table: |
| sethi $gp,hi20(sym) |
| ori $gp, $gp, lo12(sym) |
| add5.pc $gp */ |
| |
| static auto_vec<rtx_insn *, 32> nds32_group_infos; |
| /* Group the PIC and TLS relax candidate instructions for linker. */ |
| static bool |
| nds32_pic_tls_group (rtx_insn *def_insn, |
| enum nds32_relax_insn_type relax_type, |
| int sym_type) |
| { |
| df_ref def_record; |
| df_link *link; |
| rtx_insn *use_insn = NULL; |
| rtx pat, new_pat; |
| def_record = DF_INSN_DEFS (def_insn); |
| for (link = DF_REF_CHAIN (def_record); link; link = link->next) |
| { |
| if (!DF_REF_INSN_INFO (link->ref)) |
| continue; |
| |
| use_insn = DF_REF_INSN (link->ref); |
| |
| /* Skip if define insn and use insn not in the same basic block. */ |
| if (!dominated_by_p (CDI_DOMINATORS, |
| BLOCK_FOR_INSN (use_insn), |
| BLOCK_FOR_INSN (def_insn))) |
| return FALSE; |
| |
| /* Skip if use_insn not active insn. */ |
| if (!active_insn_p (use_insn)) |
| return FALSE; |
| |
| switch (relax_type) |
| { |
| case RELAX_ORI: |
| |
| /* GOTOFF, TLSLE: |
| sethi $ra, hi20(sym) |
| ori $ra, $ra, lo12(sym) |
| add $rb, $ra, $gp($tp) */ |
| if ((sym_type == UNSPEC_TLSLE |
| || sym_type == UNSPEC_GOTOFF) |
| && (recog_memoized (use_insn) == CODE_FOR_addsi3)) |
| { |
| pat = XEXP (PATTERN (use_insn), 1); |
| new_pat = |
| gen_rtx_UNSPEC (SImode, |
| gen_rtvec (2, XEXP (pat, 0), XEXP (pat, 1)), |
| UNSPEC_ADD32); |
| validate_replace_rtx (pat, new_pat, use_insn); |
| nds32_group_infos.safe_push (use_insn); |
| } |
| else if (nds32_plus_reg_load_store_p (use_insn) |
| && !nds32_sp_base_or_plus_load_store_p (use_insn)) |
| nds32_group_infos.safe_push (use_insn); |
| else |
| return FALSE; |
| break; |
| |
| default: |
| return FALSE; |
| } |
| } |
| return TRUE; |
| } |
| |
| static int |
| nds32_pic_tls_symbol_type (rtx x) |
| { |
| x = XEXP (SET_SRC (PATTERN (x)), 1); |
| |
| if (GET_CODE (x) == CONST) |
| { |
| x = XEXP (x, 0); |
| |
| if (GET_CODE (x) == PLUS) |
| x = XEXP (x, 0); |
| |
| return XINT (x, 1); |
| } |
| |
| return XINT (x, 1); |
| } |
| |
| /* Group the relax candidates with group id. */ |
| static void |
| nds32_group_insns (rtx_insn *sethi) |
| { |
| df_ref def_record, use_record; |
| df_link *link; |
| rtx_insn *use_insn = NULL; |
| rtx group_id; |
| bool valid; |
| |
| def_record = DF_INSN_DEFS (sethi); |
| |
| for (link = DF_REF_CHAIN (def_record); link; link = link->next) |
| { |
| if (!DF_REF_INSN_INFO (link->ref)) |
| continue; |
| |
| use_insn = DF_REF_INSN (link->ref); |
| |
| /* Skip if define insn and use insn not in the same basic block. */ |
| if (!dominated_by_p (CDI_DOMINATORS, |
| BLOCK_FOR_INSN (use_insn), |
| BLOCK_FOR_INSN (sethi))) |
| return; |
| |
| /* Skip if the low-part used register is from different high-part |
| instructions. */ |
| use_record = DF_INSN_USES (use_insn); |
| if (DF_REF_CHAIN (use_record) && DF_REF_CHAIN (use_record)->next) |
| return; |
| |
| /* Skip if use_insn not active insn. */ |
| if (!active_insn_p (use_insn)) |
| return; |
| |
| /* Initial use_insn_type. */ |
| if (!(recog_memoized (use_insn) == CODE_FOR_lo_sum |
| || nds32_symbol_load_store_p (use_insn) |
| || (nds32_reg_base_load_store_p (use_insn) |
| &&!nds32_sp_base_or_plus_load_store_p (use_insn)))) |
| return; |
| } |
| |
| group_id = GEN_INT (nds32_alloc_relax_group_id ()); |
| /* Insert .relax_* directive for sethi. */ |
| emit_insn_before (gen_relax_group (group_id), sethi); |
| |
| /* Scan the use insns and insert the directive. */ |
| for (link = DF_REF_CHAIN (def_record); link; link = link->next) |
| { |
| if (!DF_REF_INSN_INFO (link->ref)) |
| continue; |
| |
| use_insn = DF_REF_INSN (link->ref); |
| |
| /* Insert .relax_* directive. */ |
| if (active_insn_p (use_insn)) |
| emit_insn_before (gen_relax_group (group_id), use_insn); |
| |
| /* Find ori ra, ra, unspec(symbol) instruction. */ |
| if (use_insn != NULL |
| && recog_memoized (use_insn) == CODE_FOR_lo_sum |
| && !nds32_const_unspec_p (XEXP (SET_SRC (PATTERN (use_insn)), 1))) |
| { |
| int sym_type = nds32_pic_tls_symbol_type (use_insn); |
| valid = nds32_pic_tls_group (use_insn, RELAX_ORI, sym_type); |
| |
| /* Insert .relax_* directive. */ |
| while (!nds32_group_infos.is_empty ()) |
| { |
| use_insn = nds32_group_infos.pop (); |
| if (valid) |
| emit_insn_before (gen_relax_group (group_id), use_insn); |
| } |
| } |
| } |
| } |
| |
| /* Convert relax group id in rtl. */ |
| |
| static void |
| nds32_group_tls_insn (rtx insn) |
| { |
| rtx pat = PATTERN (insn); |
| rtx unspec_relax_group = XEXP (XVECEXP (pat, 0, 1), 0); |
| int group_id = nds32_alloc_relax_group_id (); |
| |
| while (GET_CODE (pat) != SET && GET_CODE (pat) == PARALLEL) |
| { |
| pat = XVECEXP (pat, 0, 0); |
| } |
| |
| if (GET_CODE (unspec_relax_group) == UNSPEC |
| && XINT (unspec_relax_group, 1) == UNSPEC_VOLATILE_RELAX_GROUP) |
| { |
| XVECEXP (unspec_relax_group, 0, 0) = GEN_INT (group_id); |
| } |
| } |
| |
| static bool |
| nds32_float_reg_load_store_p (rtx_insn *insn) |
| { |
| rtx pat = PATTERN (insn); |
| |
| if (get_attr_type (insn) == TYPE_FLOAD |
| && GET_CODE (pat) == SET |
| && (GET_MODE (XEXP (pat, 0)) == SFmode |
| || GET_MODE (XEXP (pat, 0)) == DFmode) |
| && MEM_P (XEXP (pat, 1))) |
| { |
| rtx addr = XEXP (XEXP (pat, 1), 0); |
| |
| /* [$ra] */ |
| if (REG_P (addr)) |
| return true; |
| /* [$ra + offset] */ |
| if (GET_CODE (addr) == PLUS |
| && REG_P (XEXP (addr, 0)) |
| && CONST_INT_P (XEXP (addr, 1))) |
| return true; |
| } |
| return false; |
| } |
| |
| |
| /* Group float load-store instructions: |
| la $ra, symbol |
| flsi $rt, [$ra + offset] */ |
| |
| static void |
| nds32_group_float_insns (rtx_insn *insn) |
| { |
| df_ref def_record, use_record; |
| df_link *link; |
| rtx_insn *use_insn = NULL; |
| rtx group_id; |
| |
| def_record = DF_INSN_DEFS (insn); |
| |
| for (link = DF_REF_CHAIN (def_record); link; link = link->next) |
| { |
| if (!DF_REF_INSN_INFO (link->ref)) |
| continue; |
| |
| use_insn = DF_REF_INSN (link->ref); |
| |
| /* Skip if define insn and use insn not in the same basic block. */ |
| if (!dominated_by_p (CDI_DOMINATORS, |
| BLOCK_FOR_INSN (use_insn), |
| BLOCK_FOR_INSN (insn))) |
| return; |
| |
| /* Skip if the low-part used register is from different high-part |
| instructions. */ |
| use_record = DF_INSN_USES (use_insn); |
| if (DF_REF_CHAIN (use_record) && DF_REF_CHAIN (use_record)->next) |
| return; |
| |
| /* Skip if use_insn not active insn. */ |
| if (!active_insn_p (use_insn)) |
| return; |
| |
| if (!nds32_float_reg_load_store_p (use_insn) |
| || find_post_update_rtx (use_insn) != -1) |
| return; |
| } |
| |
| group_id = GEN_INT (nds32_alloc_relax_group_id ()); |
| /* Insert .relax_* directive for insn. */ |
| emit_insn_before (gen_relax_group (group_id), insn); |
| |
| /* Scan the use insns and insert the directive. */ |
| for (link = DF_REF_CHAIN (def_record); link; link = link->next) |
| { |
| if (!DF_REF_INSN_INFO (link->ref)) |
| continue; |
| |
| use_insn = DF_REF_INSN (link->ref); |
| |
| /* Insert .relax_* directive. */ |
| emit_insn_before (gen_relax_group (group_id), use_insn); |
| } |
| } |
| |
| /* Group the relax candidate instructions for linker. */ |
| static void |
| nds32_relax_group (void) |
| { |
| rtx_insn *insn; |
| |
| compute_bb_for_insn (); |
| |
| df_chain_add_problem (DF_DU_CHAIN | DF_UD_CHAIN); |
| df_insn_rescan_all (); |
| df_analyze (); |
| df_set_flags (DF_DEFER_INSN_RESCAN); |
| calculate_dominance_info (CDI_DOMINATORS); |
| |
| insn = get_insns (); |
| gcc_assert (NOTE_P (insn)); |
| |
| for (insn = next_active_insn (insn); insn; insn = next_active_insn (insn)) |
| { |
| if (NONJUMP_INSN_P (insn)) |
| { |
| /* Find sethi ra, symbol instruction. */ |
| if (recog_memoized (insn) == CODE_FOR_sethi |
| && nds32_symbolic_operand (XEXP (SET_SRC (PATTERN (insn)), 0), |
| SImode) |
| && !nds32_ict_const_p (XEXP (SET_SRC (PATTERN (insn)), 0))) |
| nds32_group_insns (insn); |
| else if (recog_memoized (insn) == CODE_FOR_tls_ie) |
| nds32_group_tls_insn (insn); |
| else if (TARGET_FPU_SINGLE |
| && recog_memoized (insn) == CODE_FOR_move_addr |
| && !nds32_ict_const_p (XEXP (SET_SRC (PATTERN (insn)), 0))) |
| { |
| nds32_group_float_insns (insn); |
| } |
| } |
| else if (CALL_P (insn) && recog_memoized (insn) == CODE_FOR_tls_desc) |
| { |
| nds32_group_tls_insn (insn); |
| } |
| } |
| |
| /* We must call df_finish_pass manually because it should be invoked before |
| BB information is destroyed. Hence we cannot set the TODO_df_finish flag |
| to the pass manager. */ |
| df_insn_rescan_all (); |
| df_finish_pass (false); |
| free_dominance_info (CDI_DOMINATORS); |
| } |
| |
| static unsigned int |
| nds32_relax_opt (void) |
| { |
| if (TARGET_RELAX_HINT) |
| nds32_relax_group (); |
| return 1; |
| } |
| |
| const pass_data pass_data_nds32_relax_opt = |
| { |
| RTL_PASS, /* type */ |
| "relax_opt", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| TV_MACH_DEP, /* tv_id */ |
| 0, /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| TODO_df_finish, /* todo_flags_finish */ |
| }; |
| |
| class pass_nds32_relax_opt : public rtl_opt_pass |
| { |
| public: |
| pass_nds32_relax_opt (gcc::context *ctxt) |
| : rtl_opt_pass (pass_data_nds32_relax_opt, ctxt) |
| {} |
| |
| /* opt_pass methods: */ |
| bool gate (function *) { return TARGET_RELAX_HINT; } |
| unsigned int execute (function *) { return nds32_relax_opt (); } |
| }; |
| |
| rtl_opt_pass * |
| make_pass_nds32_relax_opt (gcc::context *ctxt) |
| { |
| return new pass_nds32_relax_opt (ctxt); |
| } |