/* relax-opt pass of Andes NDS32 cpu for GNU compiler
Copyright (C) 2012-2020 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);
}