/* 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 . */ /* ------------------------------------------------------------------------ */ #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 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); }