| /* Auxiliary functions for output asm template or expand rtl |
| pattern 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 "memmodel.h" |
| #include "tm_p.h" |
| #include "optabs.h" /* For GEN_FCN. */ |
| #include "recog.h" |
| #include "output.h" |
| #include "tm-constrs.h" |
| #include "expr.h" |
| #include "emit-rtl.h" |
| #include "explow.h" |
| #include "stringpool.h" |
| #include "attribs.h" |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| static int |
| nds32_regno_to_enable4 (unsigned regno) |
| { |
| switch (regno) |
| { |
| case 28: /* $r28/fp */ |
| return 0x8; |
| case 29: /* $r29/gp */ |
| return 0x4; |
| case 30: /* $r30/lp */ |
| return 0x2; |
| case 31: /* $r31/sp */ |
| return 0x1; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* A helper function to return character based on byte size. */ |
| static char |
| nds32_byte_to_size (int byte) |
| { |
| switch (byte) |
| { |
| case 4: |
| return 'w'; |
| case 2: |
| return 'h'; |
| case 1: |
| return 'b'; |
| default: |
| /* Normally it should not be here. */ |
| gcc_unreachable (); |
| } |
| } |
| |
| static int |
| nds32_inverse_cond_code (int code) |
| { |
| switch (code) |
| { |
| case NE: |
| return EQ; |
| case EQ: |
| return NE; |
| case GT: |
| return LE; |
| case LE: |
| return GT; |
| case GE: |
| return LT; |
| case LT: |
| return GE; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| static const char * |
| nds32_cond_code_str (int code) |
| { |
| switch (code) |
| { |
| case NE: |
| return "ne"; |
| case EQ: |
| return "eq"; |
| case GT: |
| return "gt"; |
| case LE: |
| return "le"; |
| case GE: |
| return "ge"; |
| case LT: |
| return "lt"; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| static void |
| output_cond_branch (int code, const char *suffix, bool r5_p, |
| bool long_jump_p, rtx *operands) |
| { |
| char pattern[256]; |
| const char *cond_code; |
| bool align_p = NDS32_ALIGN_P (); |
| const char *align = align_p ? "\t.align\t2\n" : ""; |
| |
| if (r5_p && REGNO (operands[2]) == 5 && TARGET_16_BIT) |
| { |
| /* This is special case for beqs38 and bnes38, |
| second operand 2 can't be $r5 and it's almost meanless, |
| however it may occur after copy propgation. */ |
| if (code == EQ) |
| { |
| /* $r5 == $r5 always taken! */ |
| if (long_jump_p) |
| snprintf (pattern, sizeof (pattern), |
| "j\t%%3"); |
| else |
| snprintf (pattern, sizeof (pattern), |
| "j8\t%%3"); |
| } |
| else |
| /* Don't output anything since $r5 != $r5 never taken! */ |
| pattern[0] = '\0'; |
| } |
| else if (long_jump_p) |
| { |
| int inverse_code = nds32_inverse_cond_code (code); |
| cond_code = nds32_cond_code_str (inverse_code); |
| |
| /* b<cond><suffix> $r0, $r1, .L0 |
| => |
| b<inverse_cond><suffix> $r0, $r1, .LCB0 |
| j .L0 |
| .LCB0: |
| |
| or |
| |
| b<cond><suffix> $r0, $r1, .L0 |
| => |
| b<inverse_cond><suffix> $r0, $r1, .LCB0 |
| j .L0 |
| .LCB0: |
| */ |
| if (r5_p && TARGET_16_BIT) |
| { |
| snprintf (pattern, sizeof (pattern), |
| "b%ss38\t %%2, .LCB%%=\n\tj\t%%3\n%s.LCB%%=:", |
| cond_code, align); |
| } |
| else |
| { |
| snprintf (pattern, sizeof (pattern), |
| "b%s%s\t%%1, %%2, .LCB%%=\n\tj\t%%3\n%s.LCB%%=:", |
| cond_code, suffix, align); |
| } |
| } |
| else |
| { |
| cond_code = nds32_cond_code_str (code); |
| if (r5_p && TARGET_16_BIT) |
| { |
| /* b<cond>s38 $r1, .L0 */ |
| snprintf (pattern, sizeof (pattern), |
| "b%ss38\t %%2, %%3", cond_code); |
| } |
| else |
| { |
| /* b<cond><suffix> $r0, $r1, .L0 */ |
| snprintf (pattern, sizeof (pattern), |
| "b%s%s\t%%1, %%2, %%3", cond_code, suffix); |
| } |
| } |
| |
| output_asm_insn (pattern, operands); |
| } |
| |
| static void |
| output_cond_branch_compare_zero (int code, const char *suffix, |
| bool long_jump_p, rtx *operands, |
| bool ta_implied_p) |
| { |
| char pattern[256]; |
| const char *cond_code; |
| bool align_p = NDS32_ALIGN_P (); |
| const char *align = align_p ? "\t.align\t2\n" : ""; |
| if (long_jump_p) |
| { |
| int inverse_code = nds32_inverse_cond_code (code); |
| cond_code = nds32_cond_code_str (inverse_code); |
| |
| if (ta_implied_p && TARGET_16_BIT) |
| { |
| /* b<cond>z<suffix> .L0 |
| => |
| b<inverse_cond>z<suffix> .LCB0 |
| j .L0 |
| .LCB0: |
| */ |
| snprintf (pattern, sizeof (pattern), |
| "b%sz%s\t.LCB%%=\n\tj\t%%2\n%s.LCB%%=:", |
| cond_code, suffix, align); |
| } |
| else |
| { |
| /* b<cond>z<suffix> $r0, .L0 |
| => |
| b<inverse_cond>z<suffix> $r0, .LCB0 |
| j .L0 |
| .LCB0: |
| */ |
| snprintf (pattern, sizeof (pattern), |
| "b%sz%s\t%%1, .LCB%%=\n\tj\t%%2\n%s.LCB%%=:", |
| cond_code, suffix, align); |
| } |
| } |
| else |
| { |
| cond_code = nds32_cond_code_str (code); |
| if (ta_implied_p && TARGET_16_BIT) |
| { |
| /* b<cond>z<suffix> .L0 */ |
| snprintf (pattern, sizeof (pattern), |
| "b%sz%s\t%%2", cond_code, suffix); |
| } |
| else |
| { |
| /* b<cond>z<suffix> $r0, .L0 */ |
| snprintf (pattern, sizeof (pattern), |
| "b%sz%s\t%%1, %%2", cond_code, suffix); |
| } |
| } |
| |
| output_asm_insn (pattern, operands); |
| } |
| |
| static void |
| nds32_split_shiftrtdi3 (rtx dst, rtx src, rtx shiftamount, bool logic_shift_p) |
| { |
| rtx src_high_part; |
| rtx dst_high_part, dst_low_part; |
| |
| dst_high_part = nds32_di_high_part_subreg (dst); |
| src_high_part = nds32_di_high_part_subreg (src); |
| dst_low_part = nds32_di_low_part_subreg (dst); |
| |
| if (CONST_INT_P (shiftamount)) |
| { |
| if (INTVAL (shiftamount) < 32) |
| { |
| if (logic_shift_p) |
| { |
| emit_insn (gen_uwext (dst_low_part, src, |
| shiftamount)); |
| emit_insn (gen_lshrsi3 (dst_high_part, src_high_part, |
| shiftamount)); |
| } |
| else |
| { |
| emit_insn (gen_wext (dst_low_part, src, |
| shiftamount)); |
| emit_insn (gen_ashrsi3 (dst_high_part, src_high_part, |
| shiftamount)); |
| } |
| } |
| else |
| { |
| rtx new_shift_amout = gen_int_mode(INTVAL (shiftamount) - 32, SImode); |
| |
| if (logic_shift_p) |
| { |
| emit_insn (gen_lshrsi3 (dst_low_part, src_high_part, |
| new_shift_amout)); |
| emit_move_insn (dst_high_part, const0_rtx); |
| } |
| else |
| { |
| emit_insn (gen_ashrsi3 (dst_low_part, src_high_part, |
| new_shift_amout)); |
| emit_insn (gen_ashrsi3 (dst_high_part, src_high_part, |
| GEN_INT (31))); |
| } |
| } |
| } |
| else |
| { |
| rtx dst_low_part_l32, dst_high_part_l32; |
| rtx dst_low_part_g32, dst_high_part_g32; |
| rtx new_shift_amout, select_reg; |
| dst_low_part_l32 = gen_reg_rtx (SImode); |
| dst_high_part_l32 = gen_reg_rtx (SImode); |
| dst_low_part_g32 = gen_reg_rtx (SImode); |
| dst_high_part_g32 = gen_reg_rtx (SImode); |
| new_shift_amout = gen_reg_rtx (SImode); |
| select_reg = gen_reg_rtx (SImode); |
| |
| emit_insn (gen_andsi3 (shiftamount, shiftamount, GEN_INT (0x3f))); |
| |
| if (logic_shift_p) |
| { |
| /* |
| if (shiftamount < 32) |
| dst_low_part = wext (src, shiftamount) |
| dst_high_part = src_high_part >> shiftamount |
| else |
| dst_low_part = src_high_part >> (shiftamount & 0x1f) |
| dst_high_part = 0 |
| */ |
| emit_insn (gen_uwext (dst_low_part_l32, src, shiftamount)); |
| emit_insn (gen_lshrsi3 (dst_high_part_l32, src_high_part, |
| shiftamount)); |
| |
| emit_insn (gen_andsi3 (new_shift_amout, shiftamount, GEN_INT (0x1f))); |
| emit_insn (gen_lshrsi3 (dst_low_part_g32, src_high_part, |
| new_shift_amout)); |
| emit_move_insn (dst_high_part_g32, const0_rtx); |
| } |
| else |
| { |
| /* |
| if (shiftamount < 32) |
| dst_low_part = wext (src, shiftamount) |
| dst_high_part = src_high_part >> shiftamount |
| else |
| dst_low_part = src_high_part >> (shiftamount & 0x1f) |
| # shift 31 for sign extend |
| dst_high_part = src_high_part >> 31 |
| */ |
| emit_insn (gen_wext (dst_low_part_l32, src, shiftamount)); |
| emit_insn (gen_ashrsi3 (dst_high_part_l32, src_high_part, |
| shiftamount)); |
| |
| emit_insn (gen_andsi3 (new_shift_amout, shiftamount, GEN_INT (0x1f))); |
| emit_insn (gen_ashrsi3 (dst_low_part_g32, src_high_part, |
| new_shift_amout)); |
| emit_insn (gen_ashrsi3 (dst_high_part_g32, src_high_part, |
| GEN_INT (31))); |
| } |
| |
| emit_insn (gen_slt_compare (select_reg, shiftamount, GEN_INT (32))); |
| |
| emit_insn (gen_cmovnsi (dst_low_part, select_reg, |
| dst_low_part_l32, dst_low_part_g32)); |
| emit_insn (gen_cmovnsi (dst_high_part, select_reg, |
| dst_high_part_l32, dst_high_part_g32)); |
| } |
| } |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| /* Auxiliary function for expand RTL pattern. */ |
| |
| enum nds32_expand_result_type |
| nds32_expand_cbranch (rtx *operands) |
| { |
| rtx tmp_reg; |
| enum rtx_code code; |
| |
| code = GET_CODE (operands[0]); |
| |
| /* If operands[2] is (const_int 0), |
| we can use beqz,bnez,bgtz,bgez,bltz,or blez instructions. |
| So we have gcc generate original template rtx. */ |
| if (GET_CODE (operands[2]) == CONST_INT) |
| if (INTVAL (operands[2]) == 0) |
| if ((code != GTU) |
| && (code != GEU) |
| && (code != LTU) |
| && (code != LEU)) |
| return EXPAND_CREATE_TEMPLATE; |
| |
| /* For other comparison, NDS32 ISA only has slt (Set-on-Less-Than) |
| behavior for the comparison, we might need to generate other |
| rtx patterns to achieve same semantic. */ |
| switch (code) |
| { |
| case GT: |
| case GTU: |
| if (GET_CODE (operands[2]) == CONST_INT) |
| { |
| /* GT reg_A, const_int => !(LT reg_A, const_int + 1) */ |
| if (optimize_size || optimize == 0) |
| tmp_reg = gen_rtx_REG (SImode, TA_REGNUM); |
| else |
| tmp_reg = gen_reg_rtx (SImode); |
| |
| /* We want to plus 1 into the integer value |
| of operands[2] to create 'slt' instruction. |
| This caculation is performed on the host machine, |
| which may be 64-bit integer. |
| So the meaning of caculation result may be |
| different from the 32-bit nds32 target. |
| |
| For example: |
| 0x7fffffff + 0x1 -> 0x80000000, |
| this value is POSITIVE on 64-bit machine, |
| but the expected value on 32-bit nds32 target |
| should be NEGATIVE value. |
| |
| Hence, instead of using GEN_INT(), we use gen_int_mode() to |
| explicitly create SImode constant rtx. */ |
| enum rtx_code cmp_code; |
| |
| rtx plus1 = gen_int_mode (INTVAL (operands[2]) + 1, SImode); |
| if (satisfies_constraint_Is15 (plus1)) |
| { |
| operands[2] = plus1; |
| cmp_code = EQ; |
| if (code == GT) |
| { |
| /* GT, use slts instruction */ |
| emit_insn ( |
| gen_slts_compare (tmp_reg, operands[1], operands[2])); |
| } |
| else |
| { |
| /* GTU, use slt instruction */ |
| emit_insn ( |
| gen_slt_compare (tmp_reg, operands[1], operands[2])); |
| } |
| } |
| else |
| { |
| cmp_code = NE; |
| if (code == GT) |
| { |
| /* GT, use slts instruction */ |
| emit_insn ( |
| gen_slts_compare (tmp_reg, operands[2], operands[1])); |
| } |
| else |
| { |
| /* GTU, use slt instruction */ |
| emit_insn ( |
| gen_slt_compare (tmp_reg, operands[2], operands[1])); |
| } |
| } |
| |
| PUT_CODE (operands[0], cmp_code); |
| operands[1] = tmp_reg; |
| operands[2] = const0_rtx; |
| emit_insn (gen_cbranchsi4 (operands[0], operands[1], |
| operands[2], operands[3])); |
| |
| return EXPAND_DONE; |
| } |
| else |
| { |
| /* GT reg_A, reg_B => LT reg_B, reg_A */ |
| if (optimize_size || optimize == 0) |
| tmp_reg = gen_rtx_REG (SImode, TA_REGNUM); |
| else |
| tmp_reg = gen_reg_rtx (SImode); |
| |
| if (code == GT) |
| { |
| /* GT, use slts instruction */ |
| emit_insn (gen_slts_compare (tmp_reg, operands[2], operands[1])); |
| } |
| else |
| { |
| /* GTU, use slt instruction */ |
| emit_insn (gen_slt_compare (tmp_reg, operands[2], operands[1])); |
| } |
| |
| PUT_CODE (operands[0], NE); |
| operands[1] = tmp_reg; |
| operands[2] = const0_rtx; |
| emit_insn (gen_cbranchsi4 (operands[0], operands[1], |
| operands[2], operands[3])); |
| |
| return EXPAND_DONE; |
| } |
| |
| case GE: |
| case GEU: |
| /* GE reg_A, reg_B => !(LT reg_A, reg_B) */ |
| /* GE reg_A, const_int => !(LT reg_A, const_int) */ |
| if (optimize_size || optimize == 0) |
| tmp_reg = gen_rtx_REG (SImode, TA_REGNUM); |
| else |
| tmp_reg = gen_reg_rtx (SImode); |
| |
| if (code == GE) |
| { |
| /* GE, use slts instruction */ |
| emit_insn (gen_slts_compare (tmp_reg, operands[1], operands[2])); |
| } |
| else |
| { |
| /* GEU, use slt instruction */ |
| emit_insn (gen_slt_compare (tmp_reg, operands[1], operands[2])); |
| } |
| |
| PUT_CODE (operands[0], EQ); |
| operands[1] = tmp_reg; |
| operands[2] = const0_rtx; |
| emit_insn (gen_cbranchsi4 (operands[0], operands[1], |
| operands[2], operands[3])); |
| |
| return EXPAND_DONE; |
| |
| case LT: |
| case LTU: |
| /* LT reg_A, reg_B => LT reg_A, reg_B */ |
| /* LT reg_A, const_int => LT reg_A, const_int */ |
| if (optimize_size || optimize == 0) |
| tmp_reg = gen_rtx_REG (SImode, TA_REGNUM); |
| else |
| tmp_reg = gen_reg_rtx (SImode); |
| |
| if (code == LT) |
| { |
| /* LT, use slts instruction */ |
| emit_insn (gen_slts_compare (tmp_reg, operands[1], operands[2])); |
| } |
| else |
| { |
| /* LTU, use slt instruction */ |
| emit_insn (gen_slt_compare (tmp_reg, operands[1], operands[2])); |
| } |
| |
| PUT_CODE (operands[0], NE); |
| operands[1] = tmp_reg; |
| operands[2] = const0_rtx; |
| emit_insn (gen_cbranchsi4 (operands[0], operands[1], |
| operands[2], operands[3])); |
| |
| return EXPAND_DONE; |
| |
| case LE: |
| case LEU: |
| if (GET_CODE (operands[2]) == CONST_INT) |
| { |
| /* LE reg_A, const_int => LT reg_A, const_int + 1 */ |
| if (optimize_size || optimize == 0) |
| tmp_reg = gen_rtx_REG (SImode, TA_REGNUM); |
| else |
| tmp_reg = gen_reg_rtx (SImode); |
| |
| enum rtx_code cmp_code; |
| /* Note that (le:SI X INT_MAX) is not the same as (lt:SI X INT_MIN). |
| We better have an assert here in case GCC does not properly |
| optimize it away. The INT_MAX here is 0x7fffffff for target. */ |
| rtx plus1 = gen_int_mode (INTVAL (operands[2]) + 1, SImode); |
| if (satisfies_constraint_Is15 (plus1)) |
| { |
| operands[2] = plus1; |
| cmp_code = NE; |
| if (code == LE) |
| { |
| /* LE, use slts instruction */ |
| emit_insn ( |
| gen_slts_compare (tmp_reg, operands[1], operands[2])); |
| } |
| else |
| { |
| /* LEU, use slt instruction */ |
| emit_insn ( |
| gen_slt_compare (tmp_reg, operands[1], operands[2])); |
| } |
| } |
| else |
| { |
| cmp_code = EQ; |
| if (code == LE) |
| { |
| /* LE, use slts instruction */ |
| emit_insn ( |
| gen_slts_compare (tmp_reg, operands[2], operands[1])); |
| } |
| else |
| { |
| /* LEU, use slt instruction */ |
| emit_insn ( |
| gen_slt_compare (tmp_reg, operands[2], operands[1])); |
| } |
| } |
| |
| PUT_CODE (operands[0], cmp_code); |
| operands[1] = tmp_reg; |
| operands[2] = const0_rtx; |
| emit_insn (gen_cbranchsi4 (operands[0], operands[1], |
| operands[2], operands[3])); |
| |
| return EXPAND_DONE; |
| } |
| else |
| { |
| /* LE reg_A, reg_B => !(LT reg_B, reg_A) */ |
| if (optimize_size || optimize == 0) |
| tmp_reg = gen_rtx_REG (SImode, TA_REGNUM); |
| else |
| tmp_reg = gen_reg_rtx (SImode); |
| |
| if (code == LE) |
| { |
| /* LE, use slts instruction */ |
| emit_insn (gen_slts_compare (tmp_reg, operands[2], operands[1])); |
| } |
| else |
| { |
| /* LEU, use slt instruction */ |
| emit_insn (gen_slt_compare (tmp_reg, operands[2], operands[1])); |
| } |
| |
| PUT_CODE (operands[0], EQ); |
| operands[1] = tmp_reg; |
| operands[2] = const0_rtx; |
| emit_insn (gen_cbranchsi4 (operands[0], operands[1], |
| operands[2], operands[3])); |
| |
| return EXPAND_DONE; |
| } |
| |
| case EQ: |
| case NE: |
| /* NDS32 ISA has various form for eq/ne behavior no matter |
| what kind of the operand is. |
| So just generate original template rtx. */ |
| |
| /* Put operands[2] into register if operands[2] is a large |
| const_int or ISAv2. */ |
| if (GET_CODE (operands[2]) == CONST_INT |
| && (!satisfies_constraint_Is11 (operands[2]) |
| || TARGET_ISA_V2)) |
| operands[2] = force_reg (SImode, operands[2]); |
| |
| return EXPAND_CREATE_TEMPLATE; |
| |
| default: |
| return EXPAND_FAIL; |
| } |
| } |
| |
| enum nds32_expand_result_type |
| nds32_expand_cstore (rtx *operands) |
| { |
| rtx tmp_reg; |
| enum rtx_code code; |
| |
| code = GET_CODE (operands[1]); |
| |
| switch (code) |
| { |
| case EQ: |
| case NE: |
| if (GET_CODE (operands[3]) == CONST_INT) |
| { |
| /* reg_R = (reg_A == const_int_B) |
| --> xori reg_C, reg_A, const_int_B |
| slti reg_R, reg_C, const_int_1 |
| reg_R = (reg_A != const_int_B) |
| --> xori reg_C, reg_A, const_int_B |
| slti reg_R, const_int0, reg_C */ |
| tmp_reg = gen_reg_rtx (SImode); |
| |
| /* If the integer value is not in the range of imm15s, |
| we need to force register first because our addsi3 pattern |
| only accept nds32_rimm15s_operand predicate. */ |
| rtx new_imm = gen_int_mode (-INTVAL (operands[3]), SImode); |
| if (satisfies_constraint_Is15 (new_imm)) |
| emit_insn (gen_addsi3 (tmp_reg, operands[2], new_imm)); |
| else |
| { |
| if (!(satisfies_constraint_Iu15 (operands[3]) |
| || (TARGET_EXT_PERF |
| && satisfies_constraint_It15 (operands[3])))) |
| operands[3] = force_reg (SImode, operands[3]); |
| emit_insn (gen_xorsi3 (tmp_reg, operands[2], operands[3])); |
| } |
| |
| if (code == EQ) |
| emit_insn (gen_slt_eq0 (operands[0], tmp_reg)); |
| else |
| emit_insn (gen_slt_compare (operands[0], const0_rtx, tmp_reg)); |
| |
| return EXPAND_DONE; |
| } |
| else |
| { |
| /* reg_R = (reg_A == reg_B) |
| --> xor reg_C, reg_A, reg_B |
| slti reg_R, reg_C, const_int_1 |
| reg_R = (reg_A != reg_B) |
| --> xor reg_C, reg_A, reg_B |
| slti reg_R, const_int0, reg_C */ |
| tmp_reg = gen_reg_rtx (SImode); |
| emit_insn (gen_xorsi3 (tmp_reg, operands[2], operands[3])); |
| if (code == EQ) |
| emit_insn (gen_slt_eq0 (operands[0], tmp_reg)); |
| else |
| emit_insn (gen_slt_compare (operands[0], const0_rtx, tmp_reg)); |
| |
| return EXPAND_DONE; |
| } |
| case GT: |
| case GTU: |
| /* reg_R = (reg_A > reg_B) --> slt reg_R, reg_B, reg_A */ |
| /* reg_R = (reg_A > const_int_B) --> slt reg_R, const_int_B, reg_A */ |
| if (code == GT) |
| { |
| /* GT, use slts instruction */ |
| emit_insn (gen_slts_compare (operands[0], operands[3], operands[2])); |
| } |
| else |
| { |
| /* GTU, use slt instruction */ |
| emit_insn (gen_slt_compare (operands[0], operands[3], operands[2])); |
| } |
| |
| return EXPAND_DONE; |
| |
| case GE: |
| case GEU: |
| if (GET_CODE (operands[3]) == CONST_INT) |
| { |
| /* reg_R = (reg_A >= const_int_B) |
| --> movi reg_C, const_int_B - 1 |
| slt reg_R, reg_C, reg_A */ |
| tmp_reg = gen_reg_rtx (SImode); |
| |
| emit_insn (gen_movsi (tmp_reg, |
| gen_int_mode (INTVAL (operands[3]) - 1, |
| SImode))); |
| if (code == GE) |
| { |
| /* GE, use slts instruction */ |
| emit_insn (gen_slts_compare (operands[0], tmp_reg, operands[2])); |
| } |
| else |
| { |
| /* GEU, use slt instruction */ |
| emit_insn (gen_slt_compare (operands[0], tmp_reg, operands[2])); |
| } |
| |
| return EXPAND_DONE; |
| } |
| else |
| { |
| /* reg_R = (reg_A >= reg_B) |
| --> slt reg_R, reg_A, reg_B |
| xori reg_R, reg_R, const_int_1 */ |
| if (code == GE) |
| { |
| /* GE, use slts instruction */ |
| emit_insn (gen_slts_compare (operands[0], |
| operands[2], operands[3])); |
| } |
| else |
| { |
| /* GEU, use slt instruction */ |
| emit_insn (gen_slt_compare (operands[0], |
| operands[2], operands[3])); |
| } |
| |
| /* perform 'not' behavior */ |
| emit_insn (gen_xorsi3 (operands[0], operands[0], const1_rtx)); |
| |
| return EXPAND_DONE; |
| } |
| |
| case LT: |
| case LTU: |
| /* reg_R = (reg_A < reg_B) --> slt reg_R, reg_A, reg_B */ |
| /* reg_R = (reg_A < const_int_B) --> slt reg_R, reg_A, const_int_B */ |
| if (code == LT) |
| { |
| /* LT, use slts instruction */ |
| emit_insn (gen_slts_compare (operands[0], operands[2], operands[3])); |
| } |
| else |
| { |
| /* LTU, use slt instruction */ |
| emit_insn (gen_slt_compare (operands[0], operands[2], operands[3])); |
| } |
| |
| return EXPAND_DONE; |
| |
| case LE: |
| case LEU: |
| if (GET_CODE (operands[3]) == CONST_INT) |
| { |
| /* reg_R = (reg_A <= const_int_B) |
| --> movi reg_C, const_int_B + 1 |
| slt reg_R, reg_A, reg_C */ |
| tmp_reg = gen_reg_rtx (SImode); |
| |
| emit_insn (gen_movsi (tmp_reg, |
| gen_int_mode (INTVAL (operands[3]) + 1, |
| SImode))); |
| if (code == LE) |
| { |
| /* LE, use slts instruction */ |
| emit_insn (gen_slts_compare (operands[0], operands[2], tmp_reg)); |
| } |
| else |
| { |
| /* LEU, use slt instruction */ |
| emit_insn (gen_slt_compare (operands[0], operands[2], tmp_reg)); |
| } |
| |
| return EXPAND_DONE; |
| } |
| else |
| { |
| /* reg_R = (reg_A <= reg_B) --> slt reg_R, reg_B, reg_A |
| xori reg_R, reg_R, const_int_1 */ |
| if (code == LE) |
| { |
| /* LE, use slts instruction */ |
| emit_insn (gen_slts_compare (operands[0], |
| operands[3], operands[2])); |
| } |
| else |
| { |
| /* LEU, use slt instruction */ |
| emit_insn (gen_slt_compare (operands[0], |
| operands[3], operands[2])); |
| } |
| |
| /* perform 'not' behavior */ |
| emit_insn (gen_xorsi3 (operands[0], operands[0], const1_rtx)); |
| |
| return EXPAND_DONE; |
| } |
| |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| void |
| nds32_expand_float_cbranch (rtx *operands) |
| { |
| enum rtx_code code = GET_CODE (operands[0]); |
| enum rtx_code new_code = code; |
| rtx cmp_op0 = operands[1]; |
| rtx cmp_op1 = operands[2]; |
| rtx tmp_reg; |
| rtx tmp; |
| |
| int reverse = 0; |
| |
| /* Main Goal: Use compare instruction + branch instruction. |
| |
| For example: |
| GT, GE: swap condition and swap operands and generate |
| compare instruction(LT, LE) + branch not equal instruction. |
| |
| UNORDERED, LT, LE, EQ: no need to change and generate |
| compare instruction(UNORDERED, LT, LE, EQ) + branch not equal instruction. |
| |
| ORDERED, NE: reverse condition and generate |
| compare instruction(EQ) + branch equal instruction. */ |
| |
| switch (code) |
| { |
| case GT: |
| case GE: |
| tmp = cmp_op0; |
| cmp_op0 = cmp_op1; |
| cmp_op1 = tmp; |
| new_code = swap_condition (new_code); |
| break; |
| case UNORDERED: |
| case LT: |
| case LE: |
| case EQ: |
| break; |
| case ORDERED: |
| case NE: |
| new_code = reverse_condition (new_code); |
| reverse = 1; |
| break; |
| case UNGT: |
| case UNGE: |
| new_code = reverse_condition_maybe_unordered (new_code); |
| reverse = 1; |
| break; |
| case UNLT: |
| case UNLE: |
| new_code = reverse_condition_maybe_unordered (new_code); |
| tmp = cmp_op0; |
| cmp_op0 = cmp_op1; |
| cmp_op1 = tmp; |
| new_code = swap_condition (new_code); |
| reverse = 1; |
| break; |
| default: |
| return; |
| } |
| |
| tmp_reg = gen_reg_rtx (SImode); |
| emit_insn (gen_rtx_SET (tmp_reg, |
| gen_rtx_fmt_ee (new_code, SImode, |
| cmp_op0, cmp_op1))); |
| |
| PUT_CODE (operands[0], reverse ? EQ : NE); |
| emit_insn (gen_cbranchsi4 (operands[0], tmp_reg, |
| const0_rtx, operands[3])); |
| } |
| |
| void |
| nds32_expand_float_cstore (rtx *operands) |
| { |
| enum rtx_code code = GET_CODE (operands[1]); |
| enum rtx_code new_code = code; |
| machine_mode mode = GET_MODE (operands[2]); |
| |
| rtx cmp_op0 = operands[2]; |
| rtx cmp_op1 = operands[3]; |
| rtx tmp; |
| |
| /* Main Goal: Use compare instruction to store value. |
| |
| For example: |
| GT, GE: swap condition and swap operands. |
| reg_R = (reg_A > reg_B) --> fcmplt reg_R, reg_B, reg_A |
| reg_R = (reg_A >= reg_B) --> fcmple reg_R, reg_B, reg_A |
| |
| LT, LE, EQ: no need to change, it is already LT, LE, EQ. |
| reg_R = (reg_A < reg_B) --> fcmplt reg_R, reg_A, reg_B |
| reg_R = (reg_A <= reg_B) --> fcmple reg_R, reg_A, reg_B |
| reg_R = (reg_A == reg_B) --> fcmpeq reg_R, reg_A, reg_B |
| |
| ORDERED: reverse condition and using xor insturction to achieve 'ORDERED'. |
| reg_R = (reg_A != reg_B) --> fcmpun reg_R, reg_A, reg_B |
| xor reg_R, reg_R, const1_rtx |
| |
| NE: reverse condition and using xor insturction to achieve 'NE'. |
| reg_R = (reg_A != reg_B) --> fcmpeq reg_R, reg_A, reg_B |
| xor reg_R, reg_R, const1_rtx */ |
| switch (code) |
| { |
| case GT: |
| case GE: |
| tmp = cmp_op0; |
| cmp_op0 = cmp_op1; |
| cmp_op1 =tmp; |
| new_code = swap_condition (new_code); |
| break; |
| case UNORDERED: |
| case LT: |
| case LE: |
| case EQ: |
| break; |
| case ORDERED: |
| if (mode == SFmode) |
| emit_insn (gen_cmpsf_un (operands[0], cmp_op0, cmp_op1)); |
| else |
| emit_insn (gen_cmpdf_un (operands[0], cmp_op0, cmp_op1)); |
| |
| emit_insn (gen_xorsi3 (operands[0], operands[0], const1_rtx)); |
| return; |
| case NE: |
| if (mode == SFmode) |
| emit_insn (gen_cmpsf_eq (operands[0], cmp_op0, cmp_op1)); |
| else |
| emit_insn (gen_cmpdf_eq (operands[0], cmp_op0, cmp_op1)); |
| |
| emit_insn (gen_xorsi3 (operands[0], operands[0], const1_rtx)); |
| return; |
| default: |
| return; |
| } |
| |
| emit_insn (gen_rtx_SET (operands[0], |
| gen_rtx_fmt_ee (new_code, SImode, |
| cmp_op0, cmp_op1))); |
| } |
| |
| enum nds32_expand_result_type |
| nds32_expand_movcc (rtx *operands) |
| { |
| enum rtx_code code = GET_CODE (operands[1]); |
| enum rtx_code new_code = code; |
| machine_mode cmp0_mode = GET_MODE (XEXP (operands[1], 0)); |
| rtx cmp_op0 = XEXP (operands[1], 0); |
| rtx cmp_op1 = XEXP (operands[1], 1); |
| rtx tmp; |
| |
| if ((GET_CODE (operands[1]) == EQ || GET_CODE (operands[1]) == NE) |
| && XEXP (operands[1], 1) == const0_rtx) |
| { |
| /* If the operands[1] rtx is already (eq X 0) or (ne X 0), |
| we have gcc generate original template rtx. */ |
| return EXPAND_CREATE_TEMPLATE; |
| } |
| else if ((TARGET_FPU_SINGLE && cmp0_mode == SFmode) |
| || (TARGET_FPU_DOUBLE && cmp0_mode == DFmode)) |
| { |
| nds32_expand_float_movcc (operands); |
| } |
| else |
| { |
| /* Since there is only 'slt'(Set when Less Than) instruction for |
| comparison in Andes ISA, the major strategy we use here is to |
| convert conditional move into 'LT + EQ' or 'LT + NE' rtx combination. |
| We design constraints properly so that the reload phase will assist |
| to make one source operand to use same register as result operand. |
| Then we can use cmovz/cmovn to catch the other source operand |
| which has different register. */ |
| int reverse = 0; |
| |
| /* Main Goal: Use 'LT + EQ' or 'LT + NE' to target "then" part |
| Strategy : Reverse condition and swap comparison operands |
| |
| For example: |
| |
| a <= b ? P : Q (LE or LEU) |
| --> a > b ? Q : P (reverse condition) |
| --> b < a ? Q : P (swap comparison operands to achieve 'LT/LTU') |
| |
| a >= b ? P : Q (GE or GEU) |
| --> a < b ? Q : P (reverse condition to achieve 'LT/LTU') |
| |
| a < b ? P : Q (LT or LTU) |
| --> (NO NEED TO CHANGE, it is already 'LT/LTU') |
| |
| a > b ? P : Q (GT or GTU) |
| --> b < a ? P : Q (swap comparison operands to achieve 'LT/LTU') */ |
| switch (code) |
| { |
| case GE: case GEU: case LE: case LEU: |
| new_code = reverse_condition (code); |
| reverse = 1; |
| break; |
| case EQ: |
| case NE: |
| /* no need to reverse condition */ |
| break; |
| default: |
| return EXPAND_FAIL; |
| } |
| |
| /* For '>' comparison operator, we swap operands |
| so that we can have 'LT/LTU' operator. */ |
| if (new_code == GT || new_code == GTU) |
| { |
| tmp = cmp_op0; |
| cmp_op0 = cmp_op1; |
| cmp_op1 = tmp; |
| |
| new_code = swap_condition (new_code); |
| } |
| |
| /* Use a temporary register to store slt/slts result. */ |
| tmp = gen_reg_rtx (SImode); |
| |
| if (new_code == EQ || new_code == NE) |
| { |
| emit_insn (gen_xorsi3 (tmp, cmp_op0, cmp_op1)); |
| /* tmp == 0 if cmp_op0 == cmp_op1. */ |
| operands[1] = gen_rtx_fmt_ee (new_code, VOIDmode, tmp, const0_rtx); |
| } |
| else |
| { |
| /* This emit_insn will create corresponding 'slt/slts' |
| insturction. */ |
| if (new_code == LT) |
| emit_insn (gen_slts_compare (tmp, cmp_op0, cmp_op1)); |
| else if (new_code == LTU) |
| emit_insn (gen_slt_compare (tmp, cmp_op0, cmp_op1)); |
| else |
| gcc_unreachable (); |
| |
| /* Change comparison semantic into (eq X 0) or (ne X 0) behavior |
| so that cmovz or cmovn will be matched later. |
| |
| For reverse condition cases, we want to create a semantic that: |
| (eq X 0) --> pick up "else" part |
| For normal cases, we want to create a semantic that: |
| (ne X 0) --> pick up "then" part |
| |
| Later we will have cmovz/cmovn instruction pattern to |
| match corresponding behavior and output instruction. */ |
| operands[1] = gen_rtx_fmt_ee (reverse ? EQ : NE, |
| VOIDmode, tmp, const0_rtx); |
| } |
| } |
| return EXPAND_CREATE_TEMPLATE; |
| } |
| |
| void |
| nds32_expand_float_movcc (rtx *operands) |
| { |
| if ((GET_CODE (operands[1]) == EQ || GET_CODE (operands[1]) == NE) |
| && GET_MODE (XEXP (operands[1], 0)) == SImode |
| && XEXP (operands[1], 1) == const0_rtx) |
| { |
| /* If the operands[1] rtx is already (eq X 0) or (ne X 0), |
| we have gcc generate original template rtx. */ |
| return; |
| } |
| else |
| { |
| enum rtx_code code = GET_CODE (operands[1]); |
| enum rtx_code new_code = code; |
| machine_mode cmp0_mode = GET_MODE (XEXP (operands[1], 0)); |
| machine_mode cmp1_mode = GET_MODE (XEXP (operands[1], 1)); |
| rtx cmp_op0 = XEXP (operands[1], 0); |
| rtx cmp_op1 = XEXP (operands[1], 1); |
| rtx tmp; |
| |
| /* Compare instruction Operations: (cmp_op0 condition cmp_op1) ? 1 : 0, |
| when result is 1, and 'reverse' be set 1 for fcmovzs instructuin. */ |
| int reverse = 0; |
| |
| /* Main Goal: Use cmpare instruction + conditional move instruction. |
| Strategy : swap condition and swap comparison operands. |
| |
| For example: |
| a > b ? P : Q (GT) |
| --> a < b ? Q : P (swap condition) |
| --> b < a ? Q : P (swap comparison operands to achieve 'GT') |
| |
| a >= b ? P : Q (GE) |
| --> a <= b ? Q : P (swap condition) |
| --> b <= a ? Q : P (swap comparison operands to achieve 'GE') |
| |
| a < b ? P : Q (LT) |
| --> (NO NEED TO CHANGE, it is already 'LT') |
| |
| a >= b ? P : Q (LE) |
| --> (NO NEED TO CHANGE, it is already 'LE') |
| |
| a == b ? P : Q (EQ) |
| --> (NO NEED TO CHANGE, it is already 'EQ') */ |
| |
| switch (code) |
| { |
| case GT: |
| case GE: |
| tmp = cmp_op0; |
| cmp_op0 = cmp_op1; |
| cmp_op1 =tmp; |
| new_code = swap_condition (new_code); |
| break; |
| case UNORDERED: |
| case LT: |
| case LE: |
| case EQ: |
| break; |
| case ORDERED: |
| case NE: |
| reverse = 1; |
| new_code = reverse_condition (new_code); |
| break; |
| case UNGT: |
| case UNGE: |
| new_code = reverse_condition_maybe_unordered (new_code); |
| reverse = 1; |
| break; |
| case UNLT: |
| case UNLE: |
| new_code = reverse_condition_maybe_unordered (new_code); |
| tmp = cmp_op0; |
| cmp_op0 = cmp_op1; |
| cmp_op1 = tmp; |
| new_code = swap_condition (new_code); |
| reverse = 1; |
| break; |
| default: |
| return; |
| } |
| |
| /* Use a temporary register to store fcmpxxs result. */ |
| tmp = gen_reg_rtx (SImode); |
| |
| /* Create float compare instruction for SFmode and DFmode, |
| other MODE using cstoresi create compare instruction. */ |
| if ((cmp0_mode == DFmode || cmp0_mode == SFmode) |
| && (cmp1_mode == DFmode || cmp1_mode == SFmode)) |
| { |
| /* This emit_insn create corresponding float compare instruction */ |
| emit_insn (gen_rtx_SET (tmp, |
| gen_rtx_fmt_ee (new_code, SImode, |
| cmp_op0, cmp_op1))); |
| } |
| else |
| { |
| /* This emit_insn using cstoresi create corresponding |
| compare instruction */ |
| PUT_CODE (operands[1], new_code); |
| emit_insn (gen_cstoresi4 (tmp, operands[1], |
| cmp_op0, cmp_op1)); |
| } |
| /* operands[1] crete corresponding condition move instruction |
| for fcmovzs and fcmovns. */ |
| operands[1] = gen_rtx_fmt_ee (reverse ? EQ : NE, |
| VOIDmode, tmp, const0_rtx); |
| } |
| } |
| |
| void |
| nds32_emit_push_fpr_callee_saved (int base_offset) |
| { |
| rtx fpu_insn; |
| rtx reg, mem; |
| unsigned int regno = cfun->machine->callee_saved_first_fpr_regno; |
| unsigned int last_fpr = cfun->machine->callee_saved_last_fpr_regno; |
| |
| while (regno <= last_fpr) |
| { |
| /* Handling two registers, using fsdi instruction. */ |
| reg = gen_rtx_REG (DFmode, regno); |
| mem = gen_frame_mem (DFmode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| base_offset)); |
| base_offset += 8; |
| regno += 2; |
| fpu_insn = emit_move_insn (mem, reg); |
| RTX_FRAME_RELATED_P (fpu_insn) = 1; |
| } |
| } |
| |
| void |
| nds32_emit_pop_fpr_callee_saved (int gpr_padding_size) |
| { |
| rtx fpu_insn; |
| rtx reg, mem, addr; |
| rtx dwarf, adjust_sp_rtx; |
| unsigned int regno = cfun->machine->callee_saved_first_fpr_regno; |
| unsigned int last_fpr = cfun->machine->callee_saved_last_fpr_regno; |
| int padding = 0; |
| |
| while (regno <= last_fpr) |
| { |
| /* Handling two registers, using fldi.bi instruction. */ |
| if ((regno + 1) >= last_fpr) |
| padding = gpr_padding_size; |
| |
| reg = gen_rtx_REG (DFmode, (regno)); |
| addr = gen_rtx_POST_MODIFY (Pmode, stack_pointer_rtx, |
| gen_rtx_PLUS (Pmode, stack_pointer_rtx, |
| GEN_INT (8 + padding))); |
| mem = gen_frame_mem (DFmode, addr); |
| regno += 2; |
| fpu_insn = emit_move_insn (reg, mem); |
| |
| adjust_sp_rtx = |
| gen_rtx_SET (stack_pointer_rtx, |
| plus_constant (Pmode, stack_pointer_rtx, |
| 8 + padding)); |
| |
| dwarf = alloc_reg_note (REG_CFA_RESTORE, reg, NULL_RTX); |
| /* Tell gcc we adjust SP in this insn. */ |
| dwarf = alloc_reg_note (REG_CFA_ADJUST_CFA, copy_rtx (adjust_sp_rtx), |
| dwarf); |
| RTX_FRAME_RELATED_P (fpu_insn) = 1; |
| REG_NOTES (fpu_insn) = dwarf; |
| } |
| } |
| |
| void |
| nds32_emit_v3pop_fpr_callee_saved (int base) |
| { |
| int fpu_base_addr = base; |
| int regno; |
| rtx fpu_insn; |
| rtx reg, mem; |
| rtx dwarf; |
| |
| regno = cfun->machine->callee_saved_first_fpr_regno; |
| while (regno <= cfun->machine->callee_saved_last_fpr_regno) |
| { |
| /* Handling two registers, using fldi instruction. */ |
| reg = gen_rtx_REG (DFmode, regno); |
| mem = gen_frame_mem (DFmode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| fpu_base_addr)); |
| fpu_base_addr += 8; |
| regno += 2; |
| fpu_insn = emit_move_insn (reg, mem); |
| dwarf = alloc_reg_note (REG_CFA_RESTORE, reg, NULL_RTX); |
| RTX_FRAME_RELATED_P (fpu_insn) = 1; |
| REG_NOTES (fpu_insn) = dwarf; |
| } |
| } |
| |
| enum nds32_expand_result_type |
| nds32_expand_extv (rtx *operands) |
| { |
| gcc_assert (CONST_INT_P (operands[2]) && CONST_INT_P (operands[3])); |
| HOST_WIDE_INT width = INTVAL (operands[2]); |
| HOST_WIDE_INT bitpos = INTVAL (operands[3]); |
| rtx dst = operands[0]; |
| rtx src = operands[1]; |
| |
| if (MEM_P (src) |
| && width == 32 |
| && (bitpos % BITS_PER_UNIT) == 0 |
| && GET_MODE_BITSIZE (GET_MODE (dst)) == width) |
| { |
| rtx newmem = adjust_address (src, GET_MODE (dst), |
| bitpos / BITS_PER_UNIT); |
| |
| rtx base_addr = force_reg (Pmode, XEXP (newmem, 0)); |
| |
| emit_insn (gen_unaligned_loadsi (dst, base_addr)); |
| |
| return EXPAND_DONE; |
| } |
| return EXPAND_FAIL; |
| } |
| |
| enum nds32_expand_result_type |
| nds32_expand_insv (rtx *operands) |
| { |
| gcc_assert (CONST_INT_P (operands[1]) && CONST_INT_P (operands[2])); |
| HOST_WIDE_INT width = INTVAL (operands[1]); |
| HOST_WIDE_INT bitpos = INTVAL (operands[2]); |
| rtx dst = operands[0]; |
| rtx src = operands[3]; |
| |
| if (MEM_P (dst) |
| && width == 32 |
| && (bitpos % BITS_PER_UNIT) == 0 |
| && GET_MODE_BITSIZE (GET_MODE (src)) == width) |
| { |
| rtx newmem = adjust_address (dst, GET_MODE (src), |
| bitpos / BITS_PER_UNIT); |
| |
| rtx base_addr = force_reg (Pmode, XEXP (newmem, 0)); |
| |
| emit_insn (gen_unaligned_storesi (base_addr, src)); |
| |
| return EXPAND_DONE; |
| } |
| return EXPAND_FAIL; |
| } |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| /* Function to generate PC relative jump table. |
| Refer to nds32.md for more details. |
| |
| The following is the sample for the case that diff value |
| can be presented in '.short' size. |
| |
| addi $r1, $r1, -(case_lower_bound) |
| slti $ta, $r1, (case_number) |
| beqz $ta, .L_skip_label |
| |
| la $ta, .L35 ! get jump table address |
| lh $r1, [$ta + $r1 << 1] ! load symbol diff from jump table entry |
| addi $ta, $r1, $ta |
| jr5 $ta |
| |
| ! jump table entry |
| L35: |
| .short .L25-.L35 |
| .short .L26-.L35 |
| .short .L27-.L35 |
| .short .L28-.L35 |
| .short .L29-.L35 |
| .short .L30-.L35 |
| .short .L31-.L35 |
| .short .L32-.L35 |
| .short .L33-.L35 |
| .short .L34-.L35 */ |
| const char * |
| nds32_output_casesi_pc_relative (rtx *operands) |
| { |
| machine_mode mode; |
| rtx diff_vec; |
| |
| diff_vec = PATTERN (NEXT_INSN (as_a <rtx_insn *> (operands[1]))); |
| |
| gcc_assert (GET_CODE (diff_vec) == ADDR_DIFF_VEC); |
| |
| /* Step C: "t <-- operands[1]". */ |
| if (flag_pic) |
| { |
| output_asm_insn ("sethi\t$ta, hi20(%l1@GOTOFF)", operands); |
| output_asm_insn ("ori\t$ta, $ta, lo12(%l1@GOTOFF)", operands); |
| output_asm_insn ("add\t$ta, $ta, $gp", operands); |
| } |
| else |
| output_asm_insn ("la\t$ta, %l1", operands); |
| |
| /* Get the mode of each element in the difference vector. */ |
| mode = GET_MODE (diff_vec); |
| |
| /* Step D: "z <-- (mem (plus (operands[0] << m) t))", |
| where m is 0, 1, or 2 to load address-diff value from table. */ |
| switch (mode) |
| { |
| case E_QImode: |
| output_asm_insn ("lb\t%2, [$ta + %0 << 0]", operands); |
| break; |
| case E_HImode: |
| output_asm_insn ("lh\t%2, [$ta + %0 << 1]", operands); |
| break; |
| case E_SImode: |
| output_asm_insn ("lw\t%2, [$ta + %0 << 2]", operands); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| |
| /* Step E: "t <-- z + t". |
| Add table label_ref with address-diff value to |
| obtain target case address. */ |
| output_asm_insn ("add\t$ta, %2, $ta", operands); |
| |
| /* Step F: jump to target with register t. */ |
| if (TARGET_16_BIT) |
| return "jr5\t$ta"; |
| else |
| return "jr\t$ta"; |
| } |
| |
| /* Function to generate normal jump table. */ |
| const char * |
| nds32_output_casesi (rtx *operands) |
| { |
| /* Step C: "t <-- operands[1]". */ |
| if (flag_pic) |
| { |
| output_asm_insn ("sethi\t$ta, hi20(%l1@GOTOFF)", operands); |
| output_asm_insn ("ori\t$ta, $ta, lo12(%l1@GOTOFF)", operands); |
| output_asm_insn ("add\t$ta, $ta, $gp", operands); |
| } |
| else |
| output_asm_insn ("la\t$ta, %l1", operands); |
| |
| /* Step D: "z <-- (mem (plus (operands[0] << 2) t))". */ |
| output_asm_insn ("lw\t%2, [$ta + %0 << 2]", operands); |
| |
| /* No need to perform Step E, which is only used for |
| pc relative jump table. */ |
| |
| /* Step F: jump to target with register z. */ |
| if (TARGET_16_BIT) |
| return "jr5\t%2"; |
| else |
| return "jr\t%2"; |
| } |
| |
| /* Function to return memory format. */ |
| enum nds32_16bit_address_type |
| nds32_mem_format (rtx op) |
| { |
| machine_mode mode_test; |
| int val; |
| int regno; |
| |
| if (!TARGET_16_BIT) |
| return ADDRESS_NOT_16BIT_FORMAT; |
| |
| mode_test = GET_MODE (op); |
| |
| op = XEXP (op, 0); |
| |
| /* 45 format. */ |
| if (GET_CODE (op) == REG |
| && ((mode_test == SImode) || (mode_test == SFmode))) |
| return ADDRESS_REG; |
| |
| /* 333 format for QI/HImode. */ |
| if (GET_CODE (op) == REG && (REGNO (op) < R8_REGNUM)) |
| return ADDRESS_LO_REG_IMM3U; |
| |
| /* post_inc 333 format. */ |
| if ((GET_CODE (op) == POST_INC) |
| && ((mode_test == SImode) || (mode_test == SFmode))) |
| { |
| regno = REGNO(XEXP (op, 0)); |
| |
| if (regno < 8) |
| return ADDRESS_POST_INC_LO_REG_IMM3U; |
| } |
| |
| /* post_inc 333 format. */ |
| if ((GET_CODE (op) == POST_MODIFY) |
| && ((mode_test == SImode) || (mode_test == SFmode)) |
| && (REG_P (XEXP (XEXP (op, 1), 0))) |
| && (CONST_INT_P (XEXP (XEXP (op, 1), 1)))) |
| { |
| regno = REGNO (XEXP (XEXP (op, 1), 0)); |
| val = INTVAL (XEXP (XEXP (op, 1), 1)); |
| if (regno < 8 && val > 0 && val < 32) |
| return ADDRESS_POST_MODIFY_LO_REG_IMM3U; |
| } |
| |
| if ((GET_CODE (op) == PLUS) |
| && (GET_CODE (XEXP (op, 0)) == REG) |
| && (GET_CODE (XEXP (op, 1)) == CONST_INT)) |
| { |
| val = INTVAL (XEXP (op, 1)); |
| |
| regno = REGNO(XEXP (op, 0)); |
| |
| if (regno > 8 |
| && regno != SP_REGNUM |
| && regno != FP_REGNUM) |
| return ADDRESS_NOT_16BIT_FORMAT; |
| |
| switch (mode_test) |
| { |
| case E_QImode: |
| /* 333 format. */ |
| if (val >= 0 && val < 8 && regno < 8) |
| return ADDRESS_LO_REG_IMM3U; |
| break; |
| |
| case E_HImode: |
| /* 333 format. */ |
| if (val >= 0 && val < 16 && (val % 2 == 0) && regno < 8) |
| return ADDRESS_LO_REG_IMM3U; |
| break; |
| |
| case E_SImode: |
| case E_SFmode: |
| case E_DFmode: |
| /* r8 imply fe format. */ |
| if ((regno == 8) && |
| (val >= -128 && val <= -4 && (val % 4 == 0))) |
| return ADDRESS_R8_IMM7U; |
| /* fp imply 37 format. */ |
| if ((regno == FP_REGNUM) && |
| (val >= 0 && val < 512 && (val % 4 == 0))) |
| return ADDRESS_FP_IMM7U; |
| /* sp imply 37 format. */ |
| else if ((regno == SP_REGNUM) && |
| (val >= 0 && val < 512 && (val % 4 == 0))) |
| return ADDRESS_SP_IMM7U; |
| /* 333 format. */ |
| else if (val >= 0 && val < 32 && (val % 4 == 0) && regno < 8) |
| return ADDRESS_LO_REG_IMM3U; |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| return ADDRESS_NOT_16BIT_FORMAT; |
| } |
| |
| /* Output 16-bit store. */ |
| const char * |
| nds32_output_16bit_store (rtx *operands, int byte) |
| { |
| char pattern[100]; |
| char size; |
| rtx code = XEXP (operands[0], 0); |
| |
| size = nds32_byte_to_size (byte); |
| |
| switch (nds32_mem_format (operands[0])) |
| { |
| case ADDRESS_REG: |
| operands[0] = code; |
| output_asm_insn ("swi450\t%1, [%0]", operands); |
| break; |
| case ADDRESS_LO_REG_IMM3U: |
| snprintf (pattern, sizeof (pattern), "s%ci333\t%%1, %%0", size); |
| output_asm_insn (pattern, operands); |
| break; |
| case ADDRESS_POST_INC_LO_REG_IMM3U: |
| snprintf (pattern, sizeof (pattern), "swi333.bi\t%%1, %%0, 4"); |
| output_asm_insn (pattern, operands); |
| break; |
| case ADDRESS_POST_MODIFY_LO_REG_IMM3U: |
| snprintf (pattern, sizeof (pattern), "swi333.bi\t%%1, %%0"); |
| output_asm_insn (pattern, operands); |
| break; |
| case ADDRESS_FP_IMM7U: |
| output_asm_insn ("swi37\t%1, %0", operands); |
| break; |
| case ADDRESS_SP_IMM7U: |
| /* Get immediate value and set back to operands[1]. */ |
| operands[0] = XEXP (code, 1); |
| output_asm_insn ("swi37.sp\t%1, [ + (%0)]", operands); |
| break; |
| default: |
| break; |
| } |
| |
| return ""; |
| } |
| |
| /* Output 16-bit load. */ |
| const char * |
| nds32_output_16bit_load (rtx *operands, int byte) |
| { |
| char pattern[100]; |
| unsigned char size; |
| rtx code = XEXP (operands[1], 0); |
| |
| size = nds32_byte_to_size (byte); |
| |
| switch (nds32_mem_format (operands[1])) |
| { |
| case ADDRESS_REG: |
| operands[1] = code; |
| output_asm_insn ("lwi450\t%0, [%1]", operands); |
| break; |
| case ADDRESS_LO_REG_IMM3U: |
| snprintf (pattern, sizeof (pattern), "l%ci333\t%%0, %%1", size); |
| output_asm_insn (pattern, operands); |
| break; |
| case ADDRESS_POST_INC_LO_REG_IMM3U: |
| snprintf (pattern, sizeof (pattern), "lwi333.bi\t%%0, %%1, 4"); |
| output_asm_insn (pattern, operands); |
| break; |
| case ADDRESS_POST_MODIFY_LO_REG_IMM3U: |
| snprintf (pattern, sizeof (pattern), "lwi333.bi\t%%0, %%1"); |
| output_asm_insn (pattern, operands); |
| break; |
| case ADDRESS_R8_IMM7U: |
| output_asm_insn ("lwi45.fe\t%0, %e1", operands); |
| break; |
| case ADDRESS_FP_IMM7U: |
| output_asm_insn ("lwi37\t%0, %1", operands); |
| break; |
| case ADDRESS_SP_IMM7U: |
| /* Get immediate value and set back to operands[0]. */ |
| operands[1] = XEXP (code, 1); |
| output_asm_insn ("lwi37.sp\t%0, [ + (%1)]", operands); |
| break; |
| default: |
| break; |
| } |
| |
| return ""; |
| } |
| |
| /* Output 32-bit store. */ |
| const char * |
| nds32_output_32bit_store (rtx *operands, int byte) |
| { |
| char pattern[100]; |
| unsigned char size; |
| rtx code = XEXP (operands[0], 0); |
| |
| size = nds32_byte_to_size (byte); |
| |
| switch (GET_CODE (code)) |
| { |
| case REG: |
| /* (mem (reg X)) |
| => access location by using register, |
| use "sbi / shi / swi" */ |
| snprintf (pattern, sizeof (pattern), "s%ci\t%%1, %%0", size); |
| break; |
| |
| case SYMBOL_REF: |
| case CONST: |
| /* (mem (symbol_ref X)) |
| (mem (const (...))) |
| => access global variables, |
| use "sbi.gp / shi.gp / swi.gp" */ |
| operands[0] = XEXP (operands[0], 0); |
| snprintf (pattern, sizeof (pattern), "s%ci.gp\t%%1, [ + %%0]", size); |
| break; |
| |
| case POST_INC: |
| /* (mem (post_inc reg)) |
| => access location by using register which will be post increment, |
| use "sbi.bi / shi.bi / swi.bi" */ |
| snprintf (pattern, sizeof (pattern), |
| "s%ci.bi\t%%1, %%0, %d", size, byte); |
| break; |
| |
| case POST_DEC: |
| /* (mem (post_dec reg)) |
| => access location by using register which will be post decrement, |
| use "sbi.bi / shi.bi / swi.bi" */ |
| snprintf (pattern, sizeof (pattern), |
| "s%ci.bi\t%%1, %%0, -%d", size, byte); |
| break; |
| |
| case POST_MODIFY: |
| switch (GET_CODE (XEXP (XEXP (code, 1), 1))) |
| { |
| case REG: |
| case SUBREG: |
| /* (mem (post_modify (reg) (plus (reg) (reg)))) |
| => access location by using register which will be |
| post modified with reg, |
| use "sb.bi/ sh.bi / sw.bi" */ |
| snprintf (pattern, sizeof (pattern), "s%c.bi\t%%1, %%0", size); |
| break; |
| case CONST_INT: |
| /* (mem (post_modify (reg) (plus (reg) (const_int)))) |
| => access location by using register which will be |
| post modified with const_int, |
| use "sbi.bi/ shi.bi / swi.bi" */ |
| snprintf (pattern, sizeof (pattern), "s%ci.bi\t%%1, %%0", size); |
| break; |
| default: |
| abort (); |
| } |
| break; |
| |
| case PLUS: |
| switch (GET_CODE (XEXP (code, 1))) |
| { |
| case REG: |
| case SUBREG: |
| /* (mem (plus reg reg)) or (mem (plus (mult reg const_int) reg)) |
| => access location by adding two registers, |
| use "sb / sh / sw" */ |
| snprintf (pattern, sizeof (pattern), "s%c\t%%1, %%0", size); |
| break; |
| case CONST_INT: |
| /* (mem (plus reg const_int)) |
| => access location by adding one register with const_int, |
| use "sbi / shi / swi" */ |
| snprintf (pattern, sizeof (pattern), "s%ci\t%%1, %%0", size); |
| break; |
| default: |
| abort (); |
| } |
| break; |
| |
| case LO_SUM: |
| operands[2] = XEXP (code, 1); |
| operands[0] = XEXP (code, 0); |
| snprintf (pattern, sizeof (pattern), |
| "s%ci\t%%1, [%%0 + lo12(%%2)]", size); |
| break; |
| |
| default: |
| abort (); |
| } |
| |
| output_asm_insn (pattern, operands); |
| return ""; |
| } |
| |
| /* Output 32-bit load. */ |
| const char * |
| nds32_output_32bit_load (rtx *operands, int byte) |
| { |
| char pattern[100]; |
| unsigned char size; |
| rtx code; |
| |
| code = XEXP (operands[1], 0); |
| |
| size = nds32_byte_to_size (byte); |
| |
| switch (GET_CODE (code)) |
| { |
| case REG: |
| /* (mem (reg X)) |
| => access location by using register, |
| use "lbi / lhi / lwi" */ |
| snprintf (pattern, sizeof (pattern), "l%ci\t%%0, %%1", size); |
| break; |
| |
| case SYMBOL_REF: |
| case CONST: |
| /* (mem (symbol_ref X)) |
| (mem (const (...))) |
| => access global variables, |
| use "lbi.gp / lhi.gp / lwi.gp" */ |
| operands[1] = XEXP (operands[1], 0); |
| snprintf (pattern, sizeof (pattern), "l%ci.gp\t%%0, [ + %%1]", size); |
| break; |
| |
| case POST_INC: |
| /* (mem (post_inc reg)) |
| => access location by using register which will be post increment, |
| use "lbi.bi / lhi.bi / lwi.bi" */ |
| snprintf (pattern, sizeof (pattern), |
| "l%ci.bi\t%%0, %%1, %d", size, byte); |
| break; |
| |
| case POST_DEC: |
| /* (mem (post_dec reg)) |
| => access location by using register which will be post decrement, |
| use "lbi.bi / lhi.bi / lwi.bi" */ |
| snprintf (pattern, sizeof (pattern), |
| "l%ci.bi\t%%0, %%1, -%d", size, byte); |
| break; |
| |
| case POST_MODIFY: |
| switch (GET_CODE (XEXP (XEXP (code, 1), 1))) |
| { |
| case REG: |
| case SUBREG: |
| /* (mem (post_modify (reg) (plus (reg) (reg)))) |
| => access location by using register which will be |
| post modified with reg, |
| use "lb.bi/ lh.bi / lw.bi" */ |
| snprintf (pattern, sizeof (pattern), "l%c.bi\t%%0, %%1", size); |
| break; |
| case CONST_INT: |
| /* (mem (post_modify (reg) (plus (reg) (const_int)))) |
| => access location by using register which will be |
| post modified with const_int, |
| use "lbi.bi/ lhi.bi / lwi.bi" */ |
| snprintf (pattern, sizeof (pattern), "l%ci.bi\t%%0, %%1", size); |
| break; |
| default: |
| abort (); |
| } |
| break; |
| |
| case PLUS: |
| switch (GET_CODE (XEXP (code, 1))) |
| { |
| case REG: |
| case SUBREG: |
| /* (mem (plus reg reg)) or (mem (plus (mult reg const_int) reg)) |
| use "lb / lh / lw" */ |
| snprintf (pattern, sizeof (pattern), "l%c\t%%0, %%1", size); |
| break; |
| case CONST_INT: |
| /* (mem (plus reg const_int)) |
| => access location by adding one register with const_int, |
| use "lbi / lhi / lwi" */ |
| snprintf (pattern, sizeof (pattern), "l%ci\t%%0, %%1", size); |
| break; |
| default: |
| abort (); |
| } |
| break; |
| |
| case LO_SUM: |
| operands[2] = XEXP (code, 1); |
| operands[1] = XEXP (code, 0); |
| snprintf (pattern, sizeof (pattern), |
| "l%ci\t%%0, [%%1 + lo12(%%2)]", size); |
| break; |
| |
| default: |
| abort (); |
| } |
| |
| output_asm_insn (pattern, operands); |
| return ""; |
| } |
| |
| /* Output 32-bit load with signed extension. */ |
| const char * |
| nds32_output_32bit_load_s (rtx *operands, int byte) |
| { |
| char pattern[100]; |
| unsigned char size; |
| rtx code; |
| |
| code = XEXP (operands[1], 0); |
| |
| size = nds32_byte_to_size (byte); |
| |
| switch (GET_CODE (code)) |
| { |
| case REG: |
| /* (mem (reg X)) |
| => access location by using register, |
| use "lbsi / lhsi" */ |
| snprintf (pattern, sizeof (pattern), "l%csi\t%%0, %%1", size); |
| break; |
| |
| case SYMBOL_REF: |
| case CONST: |
| /* (mem (symbol_ref X)) |
| (mem (const (...))) |
| => access global variables, |
| use "lbsi.gp / lhsi.gp" */ |
| operands[1] = XEXP (operands[1], 0); |
| snprintf (pattern, sizeof (pattern), "l%csi.gp\t%%0, [ + %%1]", size); |
| break; |
| |
| case POST_INC: |
| /* (mem (post_inc reg)) |
| => access location by using register which will be post increment, |
| use "lbsi.bi / lhsi.bi" */ |
| snprintf (pattern, sizeof (pattern), |
| "l%csi.bi\t%%0, %%1, %d", size, byte); |
| break; |
| |
| case POST_DEC: |
| /* (mem (post_dec reg)) |
| => access location by using register which will be post decrement, |
| use "lbsi.bi / lhsi.bi" */ |
| snprintf (pattern, sizeof (pattern), |
| "l%csi.bi\t%%0, %%1, -%d", size, byte); |
| break; |
| |
| case POST_MODIFY: |
| switch (GET_CODE (XEXP (XEXP (code, 1), 1))) |
| { |
| case REG: |
| case SUBREG: |
| /* (mem (post_modify (reg) (plus (reg) (reg)))) |
| => access location by using register which will be |
| post modified with reg, |
| use "lbs.bi/ lhs.bi" */ |
| snprintf (pattern, sizeof (pattern), "l%cs.bi\t%%0, %%1", size); |
| break; |
| case CONST_INT: |
| /* (mem (post_modify (reg) (plus (reg) (const_int)))) |
| => access location by using register which will be |
| post modified with const_int, |
| use "lbsi.bi/ lhsi.bi" */ |
| snprintf (pattern, sizeof (pattern), "l%csi.bi\t%%0, %%1", size); |
| break; |
| default: |
| abort (); |
| } |
| break; |
| |
| case PLUS: |
| switch (GET_CODE (XEXP (code, 1))) |
| { |
| case REG: |
| case SUBREG: |
| /* (mem (plus reg reg)) or (mem (plus (mult reg const_int) reg)) |
| use "lbs / lhs" */ |
| snprintf (pattern, sizeof (pattern), "l%cs\t%%0, %%1", size); |
| break; |
| case CONST_INT: |
| /* (mem (plus reg const_int)) |
| => access location by adding one register with const_int, |
| use "lbsi / lhsi" */ |
| snprintf (pattern, sizeof (pattern), "l%csi\t%%0, %%1", size); |
| break; |
| default: |
| abort (); |
| } |
| break; |
| |
| case LO_SUM: |
| operands[2] = XEXP (code, 1); |
| operands[1] = XEXP (code, 0); |
| snprintf (pattern, sizeof (pattern), |
| "l%csi\t%%0, [%%1 + lo12(%%2)]", size); |
| break; |
| |
| default: |
| abort (); |
| } |
| |
| output_asm_insn (pattern, operands); |
| return ""; |
| } |
| |
| /* Function to output stack push operation. |
| We need to deal with normal stack push multiple or stack v3push. */ |
| const char * |
| nds32_output_stack_push (rtx par_rtx) |
| { |
| /* A string pattern for output_asm_insn(). */ |
| char pattern[100]; |
| /* The operands array which will be used in output_asm_insn(). */ |
| rtx operands[3]; |
| /* Pick up varargs first regno and last regno for further use. */ |
| int rb_va_args = cfun->machine->va_args_first_regno; |
| int re_va_args = cfun->machine->va_args_last_regno; |
| int last_argument_regno = NDS32_FIRST_GPR_REGNUM |
| + NDS32_MAX_GPR_REGS_FOR_ARGS |
| - 1; |
| /* Pick up first and last eh data regno for further use. */ |
| int rb_eh_data = cfun->machine->eh_return_data_first_regno; |
| int re_eh_data = cfun->machine->eh_return_data_last_regno; |
| int first_eh_data_regno = EH_RETURN_DATA_REGNO (0); |
| /* Pick up callee-saved first regno and last regno for further use. */ |
| int rb_callee_saved = cfun->machine->callee_saved_first_gpr_regno; |
| int re_callee_saved = cfun->machine->callee_saved_last_gpr_regno; |
| |
| /* First we need to check if we are pushing argument registers not used |
| for the named arguments. If so, we have to create 'smw.adm' (push.s) |
| instruction. */ |
| if (reg_mentioned_p (gen_rtx_REG (SImode, last_argument_regno), par_rtx)) |
| { |
| /* Set operands[0] and operands[1]. */ |
| operands[0] = gen_rtx_REG (SImode, rb_va_args); |
| operands[1] = gen_rtx_REG (SImode, re_va_args); |
| /* Create assembly code pattern: "Rb, Re, { }". */ |
| snprintf (pattern, sizeof (pattern), "push.s\t%s", "%0, %1, { }"); |
| /* We use output_asm_insn() to output assembly code by ourself. */ |
| output_asm_insn (pattern, operands); |
| return ""; |
| } |
| |
| /* If last_argument_regno is not mentioned in par_rtx, we can confirm that |
| we do not need to push argument registers for variadic function. |
| But we still need to check if we need to push exception handling |
| data registers. */ |
| if (reg_mentioned_p (gen_rtx_REG (SImode, first_eh_data_regno), par_rtx)) |
| { |
| /* Set operands[0] and operands[1]. */ |
| operands[0] = gen_rtx_REG (SImode, rb_eh_data); |
| operands[1] = gen_rtx_REG (SImode, re_eh_data); |
| /* Create assembly code pattern: "Rb, Re, { }". */ |
| snprintf (pattern, sizeof (pattern), "push.s\t%s", "%0, %1, { }"); |
| /* We use output_asm_insn() to output assembly code by ourself. */ |
| output_asm_insn (pattern, operands); |
| return ""; |
| } |
| |
| /* If we step here, we are going to do v3push or multiple push operation. */ |
| |
| /* Refer to nds32.h, where we comment when push25/pop25 are available. */ |
| if (NDS32_V3PUSH_AVAILABLE_P) |
| { |
| /* For stack v3push: |
| operands[0]: Re |
| operands[1]: imm8u */ |
| |
| /* This variable is to check if 'push25 Re,imm8u' is available. */ |
| int sp_adjust; |
| |
| /* Set operands[0]. */ |
| operands[0] = gen_rtx_REG (SImode, re_callee_saved); |
| |
| /* Check if we can generate 'push25 Re,imm8u', |
| otherwise, generate 'push25 Re,0'. */ |
| sp_adjust = cfun->machine->local_size |
| + cfun->machine->out_args_size |
| + cfun->machine->callee_saved_area_gpr_padding_bytes |
| + cfun->machine->callee_saved_fpr_regs_size; |
| if (satisfies_constraint_Iu08 (GEN_INT (sp_adjust)) |
| && NDS32_DOUBLE_WORD_ALIGN_P (sp_adjust)) |
| operands[1] = GEN_INT (sp_adjust); |
| else |
| { |
| /* Allocate callee saved fpr space. */ |
| if (cfun->machine->callee_saved_first_fpr_regno != SP_REGNUM) |
| { |
| sp_adjust = cfun->machine->callee_saved_area_gpr_padding_bytes |
| + cfun->machine->callee_saved_fpr_regs_size; |
| operands[1] = GEN_INT (sp_adjust); |
| } |
| else |
| { |
| operands[1] = GEN_INT (0); |
| } |
| } |
| |
| /* Create assembly code pattern. */ |
| snprintf (pattern, sizeof (pattern), "push25\t%%0, %%1"); |
| } |
| else |
| { |
| /* For normal stack push multiple: |
| operands[0]: Rb |
| operands[1]: Re |
| operands[2]: En4 */ |
| |
| /* This variable is used to check if we only need to generate En4 field. |
| As long as Rb==Re=SP_REGNUM, we set this variable to 1. */ |
| int push_en4_only_p = 0; |
| |
| /* Set operands[0] and operands[1]. */ |
| operands[0] = gen_rtx_REG (SImode, rb_callee_saved); |
| operands[1] = gen_rtx_REG (SImode, re_callee_saved); |
| |
| /* 'smw.adm $sp,[$sp],$sp,0' means push nothing. */ |
| if (!cfun->machine->fp_size |
| && !cfun->machine->gp_size |
| && !cfun->machine->lp_size |
| && REGNO (operands[0]) == SP_REGNUM |
| && REGNO (operands[1]) == SP_REGNUM) |
| { |
| /* No need to generate instruction. */ |
| return ""; |
| } |
| else |
| { |
| /* If Rb==Re=SP_REGNUM, we only need to generate En4 field. */ |
| if (REGNO (operands[0]) == SP_REGNUM |
| && REGNO (operands[1]) == SP_REGNUM) |
| push_en4_only_p = 1; |
| |
| /* Create assembly code pattern. |
| We need to handle the form: "Rb, Re, { $fp $gp $lp }". */ |
| snprintf (pattern, sizeof (pattern), |
| "push.s\t%s{%s%s%s }", |
| push_en4_only_p ? "" : "%0, %1, ", |
| cfun->machine->fp_size ? " $fp" : "", |
| cfun->machine->gp_size ? " $gp" : "", |
| cfun->machine->lp_size ? " $lp" : ""); |
| } |
| } |
| |
| /* We use output_asm_insn() to output assembly code by ourself. */ |
| output_asm_insn (pattern, operands); |
| return ""; |
| } |
| |
| /* Function to output stack pop operation. |
| We need to deal with normal stack pop multiple or stack v3pop. */ |
| const char * |
| nds32_output_stack_pop (rtx par_rtx ATTRIBUTE_UNUSED) |
| { |
| /* A string pattern for output_asm_insn(). */ |
| char pattern[100]; |
| /* The operands array which will be used in output_asm_insn(). */ |
| rtx operands[3]; |
| /* Pick up first and last eh data regno for further use. */ |
| int rb_eh_data = cfun->machine->eh_return_data_first_regno; |
| int re_eh_data = cfun->machine->eh_return_data_last_regno; |
| int first_eh_data_regno = EH_RETURN_DATA_REGNO (0); |
| /* Pick up callee-saved first regno and last regno for further use. */ |
| int rb_callee_saved = cfun->machine->callee_saved_first_gpr_regno; |
| int re_callee_saved = cfun->machine->callee_saved_last_gpr_regno; |
| |
| /* We need to check if we need to push exception handling |
| data registers. */ |
| if (reg_mentioned_p (gen_rtx_REG (SImode, first_eh_data_regno), par_rtx)) |
| { |
| /* Set operands[0] and operands[1]. */ |
| operands[0] = gen_rtx_REG (SImode, rb_eh_data); |
| operands[1] = gen_rtx_REG (SImode, re_eh_data); |
| /* Create assembly code pattern: "Rb, Re, { }". */ |
| snprintf (pattern, sizeof (pattern), "pop.s\t%s", "%0, %1, { }"); |
| /* We use output_asm_insn() to output assembly code by ourself. */ |
| output_asm_insn (pattern, operands); |
| return ""; |
| } |
| |
| /* If we step here, we are going to do v3pop or multiple pop operation. */ |
| |
| /* Refer to nds32.h, where we comment when push25/pop25 are available. */ |
| if (NDS32_V3PUSH_AVAILABLE_P) |
| { |
| /* For stack v3pop: |
| operands[0]: Re |
| operands[1]: imm8u */ |
| |
| /* This variable is to check if 'pop25 Re,imm8u' is available. */ |
| int sp_adjust; |
| |
| /* Set operands[0]. */ |
| operands[0] = gen_rtx_REG (SImode, re_callee_saved); |
| |
| /* Check if we can generate 'pop25 Re,imm8u', |
| otherwise, generate 'pop25 Re,0'. |
| We have to consider alloca issue as well. |
| If the function does call alloca(), the stack pointer is not fixed. |
| In that case, we cannot use 'pop25 Re,imm8u' directly. |
| We have to caculate stack pointer from frame pointer |
| and then use 'pop25 Re,0'. */ |
| sp_adjust = cfun->machine->local_size |
| + cfun->machine->out_args_size |
| + cfun->machine->callee_saved_area_gpr_padding_bytes |
| + cfun->machine->callee_saved_fpr_regs_size; |
| if (satisfies_constraint_Iu08 (GEN_INT (sp_adjust)) |
| && NDS32_DOUBLE_WORD_ALIGN_P (sp_adjust) |
| && !cfun->calls_alloca) |
| operands[1] = GEN_INT (sp_adjust); |
| else |
| { |
| if (cfun->machine->callee_saved_first_fpr_regno != SP_REGNUM) |
| { |
| /* If has fpr need to restore, the $sp on callee saved fpr |
| position, so we need to consider gpr pading bytes and |
| callee saved fpr size. */ |
| sp_adjust = cfun->machine->callee_saved_area_gpr_padding_bytes |
| + cfun->machine->callee_saved_fpr_regs_size; |
| operands[1] = GEN_INT (sp_adjust); |
| } |
| else |
| { |
| operands[1] = GEN_INT (0); |
| } |
| } |
| |
| /* Create assembly code pattern. */ |
| snprintf (pattern, sizeof (pattern), "pop25\t%%0, %%1"); |
| } |
| else |
| { |
| /* For normal stack pop multiple: |
| operands[0]: Rb |
| operands[1]: Re |
| operands[2]: En4 */ |
| |
| /* This variable is used to check if we only need to generate En4 field. |
| As long as Rb==Re=SP_REGNUM, we set this variable to 1. */ |
| int pop_en4_only_p = 0; |
| |
| /* Set operands[0] and operands[1]. */ |
| operands[0] = gen_rtx_REG (SImode, rb_callee_saved); |
| operands[1] = gen_rtx_REG (SImode, re_callee_saved); |
| |
| /* 'lmw.bim $sp,[$sp],$sp,0' means pop nothing. */ |
| if (!cfun->machine->fp_size |
| && !cfun->machine->gp_size |
| && !cfun->machine->lp_size |
| && REGNO (operands[0]) == SP_REGNUM |
| && REGNO (operands[1]) == SP_REGNUM) |
| { |
| /* No need to generate instruction. */ |
| return ""; |
| } |
| else |
| { |
| /* If Rb==Re=SP_REGNUM, we only need to generate En4 field. */ |
| if (REGNO (operands[0]) == SP_REGNUM |
| && REGNO (operands[1]) == SP_REGNUM) |
| pop_en4_only_p = 1; |
| |
| /* Create assembly code pattern. |
| We need to handle the form: "Rb, Re, { $fp $gp $lp }". */ |
| snprintf (pattern, sizeof (pattern), |
| "pop.s\t%s{%s%s%s }", |
| pop_en4_only_p ? "" : "%0, %1, ", |
| cfun->machine->fp_size ? " $fp" : "", |
| cfun->machine->gp_size ? " $gp" : "", |
| cfun->machine->lp_size ? " $lp" : ""); |
| } |
| } |
| |
| /* We use output_asm_insn() to output assembly code by ourself. */ |
| output_asm_insn (pattern, operands); |
| return ""; |
| } |
| |
| /* Function to output return operation. */ |
| const char * |
| nds32_output_return (void) |
| { |
| /* A string pattern for output_asm_insn(). */ |
| char pattern[100]; |
| /* The operands array which will be used in output_asm_insn(). */ |
| rtx operands[2]; |
| /* For stack v3pop: |
| operands[0]: Re |
| operands[1]: imm8u */ |
| int re_callee_saved = cfun->machine->callee_saved_last_gpr_regno; |
| int sp_adjust; |
| |
| /* Set operands[0]. */ |
| operands[0] = gen_rtx_REG (SImode, re_callee_saved); |
| |
| /* Check if we can generate 'pop25 Re,imm8u', |
| otherwise, generate 'pop25 Re,0'. |
| We have to consider alloca issue as well. |
| If the function does call alloca(), the stack pointer is not fixed. |
| In that case, we cannot use 'pop25 Re,imm8u' directly. |
| We have to caculate stack pointer from frame pointer |
| and then use 'pop25 Re,0'. */ |
| sp_adjust = cfun->machine->local_size |
| + cfun->machine->out_args_size |
| + cfun->machine->callee_saved_area_gpr_padding_bytes |
| + cfun->machine->callee_saved_fpr_regs_size; |
| if (satisfies_constraint_Iu08 (GEN_INT (sp_adjust)) |
| && NDS32_DOUBLE_WORD_ALIGN_P (sp_adjust) |
| && !cfun->calls_alloca) |
| operands[1] = GEN_INT (sp_adjust); |
| else |
| operands[1] = GEN_INT (0); |
| |
| /* Create assembly code pattern. */ |
| snprintf (pattern, sizeof (pattern), "pop25\t%%0, %%1"); |
| /* We use output_asm_insn() to output assembly code by ourself. */ |
| output_asm_insn (pattern, operands); |
| return ""; |
| } |
| |
| |
| /* output a float load instruction */ |
| const char * |
| nds32_output_float_load (rtx *operands) |
| { |
| char buff[100]; |
| const char *pattern; |
| rtx addr, addr_op0, addr_op1; |
| int dp = GET_MODE_SIZE (GET_MODE (operands[0])) == 8; |
| addr = XEXP (operands[1], 0); |
| switch (GET_CODE (addr)) |
| { |
| case REG: |
| pattern = "fl%ci\t%%0, %%1"; |
| break; |
| |
| case PLUS: |
| addr_op0 = XEXP (addr, 0); |
| addr_op1 = XEXP (addr, 1); |
| |
| if (REG_P (addr_op0) && REG_P (addr_op1)) |
| pattern = "fl%c\t%%0, %%1"; |
| else if (REG_P (addr_op0) && CONST_INT_P (addr_op1)) |
| pattern = "fl%ci\t%%0, %%1"; |
| else if (GET_CODE (addr_op0) == MULT && REG_P (addr_op1) |
| && REG_P (XEXP (addr_op0, 0)) |
| && CONST_INT_P (XEXP (addr_op0, 1))) |
| pattern = "fl%c\t%%0, %%1"; |
| else |
| gcc_unreachable (); |
| break; |
| |
| case POST_MODIFY: |
| addr_op0 = XEXP (addr, 0); |
| addr_op1 = XEXP (addr, 1); |
| |
| if (REG_P (addr_op0) && GET_CODE (addr_op1) == PLUS |
| && REG_P (XEXP (addr_op1, 1))) |
| pattern = "fl%c.bi\t%%0, %%1"; |
| else if (REG_P (addr_op0) && GET_CODE (addr_op1) == PLUS |
| && CONST_INT_P (XEXP (addr_op1, 1))) |
| pattern = "fl%ci.bi\t%%0, %%1"; |
| else |
| gcc_unreachable (); |
| break; |
| |
| case POST_INC: |
| if (REG_P (XEXP (addr, 0))) |
| { |
| if (dp) |
| pattern = "fl%ci.bi\t%%0, %%1, 8"; |
| else |
| pattern = "fl%ci.bi\t%%0, %%1, 4"; |
| } |
| else |
| gcc_unreachable (); |
| break; |
| |
| case POST_DEC: |
| if (REG_P (XEXP (addr, 0))) |
| { |
| if (dp) |
| pattern = "fl%ci.bi\t%%0, %%1, -8"; |
| else |
| pattern = "fl%ci.bi\t%%0, %%1, -4"; |
| } |
| else |
| gcc_unreachable (); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| sprintf (buff, pattern, dp ? 'd' : 's'); |
| output_asm_insn (buff, operands); |
| return ""; |
| } |
| |
| /* output a float store instruction */ |
| const char * |
| nds32_output_float_store (rtx *operands) |
| { |
| char buff[100]; |
| const char *pattern; |
| rtx addr, addr_op0, addr_op1; |
| int dp = GET_MODE_SIZE (GET_MODE (operands[0])) == 8; |
| addr = XEXP (operands[0], 0); |
| switch (GET_CODE (addr)) |
| { |
| case REG: |
| pattern = "fs%ci\t%%1, %%0"; |
| break; |
| |
| case PLUS: |
| addr_op0 = XEXP (addr, 0); |
| addr_op1 = XEXP (addr, 1); |
| |
| if (REG_P (addr_op0) && REG_P (addr_op1)) |
| pattern = "fs%c\t%%1, %%0"; |
| else if (REG_P (addr_op0) && CONST_INT_P (addr_op1)) |
| pattern = "fs%ci\t%%1, %%0"; |
| else if (GET_CODE (addr_op0) == MULT && REG_P (addr_op1) |
| && REG_P (XEXP (addr_op0, 0)) |
| && CONST_INT_P (XEXP (addr_op0, 1))) |
| pattern = "fs%c\t%%1, %%0"; |
| else |
| gcc_unreachable (); |
| break; |
| |
| case POST_MODIFY: |
| addr_op0 = XEXP (addr, 0); |
| addr_op1 = XEXP (addr, 1); |
| |
| if (REG_P (addr_op0) && GET_CODE (addr_op1) == PLUS |
| && REG_P (XEXP (addr_op1, 1))) |
| pattern = "fs%c.bi\t%%1, %%0"; |
| else if (REG_P (addr_op0) && GET_CODE (addr_op1) == PLUS |
| && CONST_INT_P (XEXP (addr_op1, 1))) |
| pattern = "fs%ci.bi\t%%1, %%0"; |
| else |
| gcc_unreachable (); |
| break; |
| |
| case POST_INC: |
| if (REG_P (XEXP (addr, 0))) |
| { |
| if (dp) |
| pattern = "fs%ci.bi\t%%1, %%0, 8"; |
| else |
| pattern = "fs%ci.bi\t%%1, %%0, 4"; |
| } |
| else |
| gcc_unreachable (); |
| break; |
| |
| case POST_DEC: |
| if (REG_P (XEXP (addr, 0))) |
| { |
| if (dp) |
| pattern = "fs%ci.bi\t%%1, %%0, -8"; |
| else |
| pattern = "fs%ci.bi\t%%1, %%0, -4"; |
| } |
| else |
| gcc_unreachable (); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| sprintf (buff, pattern, dp ? 'd' : 's'); |
| output_asm_insn (buff, operands); |
| return ""; |
| } |
| |
| const char * |
| nds32_output_smw_single_word (rtx *operands) |
| { |
| char buff[100]; |
| unsigned regno; |
| int enable4; |
| bool update_base_p; |
| rtx base_addr = operands[0]; |
| rtx base_reg; |
| rtx otherops[2]; |
| |
| if (REG_P (XEXP (base_addr, 0))) |
| { |
| update_base_p = false; |
| base_reg = XEXP (base_addr, 0); |
| } |
| else |
| { |
| update_base_p = true; |
| base_reg = XEXP (XEXP (base_addr, 0), 0); |
| } |
| |
| const char *update_base = update_base_p ? "m" : ""; |
| |
| regno = REGNO (operands[1]); |
| |
| otherops[0] = base_reg; |
| otherops[1] = operands[1]; |
| |
| if (regno >= 28) |
| { |
| enable4 = nds32_regno_to_enable4 (regno); |
| sprintf (buff, "smw.bi%s\t$sp, [%%0], $sp, %x", update_base, enable4); |
| } |
| else |
| { |
| sprintf (buff, "smw.bi%s\t%%1, [%%0], %%1", update_base); |
| } |
| output_asm_insn (buff, otherops); |
| return ""; |
| } |
| |
| /* ------------------------------------------------------------------------ */ |
| const char * |
| nds32_output_smw_double_word (rtx *operands) |
| { |
| char buff[100]; |
| unsigned regno; |
| int enable4; |
| bool update_base_p; |
| rtx base_addr = operands[0]; |
| rtx base_reg; |
| rtx otherops[3]; |
| |
| if (REG_P (XEXP (base_addr, 0))) |
| { |
| update_base_p = false; |
| base_reg = XEXP (base_addr, 0); |
| } |
| else |
| { |
| update_base_p = true; |
| base_reg = XEXP (XEXP (base_addr, 0), 0); |
| } |
| |
| const char *update_base = update_base_p ? "m" : ""; |
| |
| regno = REGNO (operands[1]); |
| |
| otherops[0] = base_reg; |
| otherops[1] = operands[1]; |
| otherops[2] = gen_rtx_REG (SImode, REGNO (operands[1]) + 1);; |
| |
| if (regno >= 28) |
| { |
| enable4 = nds32_regno_to_enable4 (regno) |
| | nds32_regno_to_enable4 (regno + 1); |
| sprintf (buff, "smw.bi%s\t$sp, [%%0], $sp, %x", update_base, enable4); |
| } |
| else if (regno == 27) |
| { |
| enable4 = nds32_regno_to_enable4 (regno + 1); |
| sprintf (buff, "smw.bi%s\t%%1, [%%0], %%1, %x", update_base, enable4); |
| } |
| else |
| { |
| sprintf (buff, "smw.bi%s\t%%1, [%%0], %%2", update_base); |
| } |
| output_asm_insn (buff, otherops); |
| return ""; |
| } |
| |
| const char * |
| nds32_output_lmw_single_word (rtx *operands) |
| { |
| char buff[100]; |
| unsigned regno; |
| bool update_base_p; |
| int enable4; |
| rtx base_addr = operands[1]; |
| rtx base_reg; |
| rtx otherops[2]; |
| |
| if (REG_P (XEXP (base_addr, 0))) |
| { |
| update_base_p = false; |
| base_reg = XEXP (base_addr, 0); |
| } |
| else |
| { |
| update_base_p = true; |
| base_reg = XEXP (XEXP (base_addr, 0), 0); |
| } |
| |
| const char *update_base = update_base_p ? "m" : ""; |
| |
| regno = REGNO (operands[0]); |
| |
| otherops[0] = operands[0]; |
| otherops[1] = base_reg; |
| |
| if (regno >= 28) |
| { |
| enable4 = nds32_regno_to_enable4 (regno); |
| sprintf (buff, "lmw.bi%s\t$sp, [%%1], $sp, %x", update_base, enable4); |
| } |
| else |
| { |
| sprintf (buff, "lmw.bi%s\t%%0, [%%1], %%0", update_base); |
| } |
| output_asm_insn (buff, otherops); |
| return ""; |
| } |
| |
| void |
| nds32_expand_unaligned_load (rtx *operands, enum machine_mode mode) |
| { |
| /* Initial memory offset. */ |
| int offset = WORDS_BIG_ENDIAN ? GET_MODE_SIZE (mode) - 1 : 0; |
| int offset_adj = WORDS_BIG_ENDIAN ? -1 : 1; |
| /* Initial register shift byte. */ |
| int shift = 0; |
| /* The first load byte instruction is not the same. */ |
| int width = GET_MODE_SIZE (mode) - 1; |
| rtx mem[2]; |
| rtx reg[2]; |
| rtx sub_reg; |
| rtx temp_reg, temp_sub_reg; |
| int num_reg; |
| |
| /* Generating a series of load byte instructions. |
| The first load byte instructions and other |
| load byte instructions are not the same. like: |
| First: |
| lbi reg0, [mem] |
| zeh reg0, reg0 |
| Second: |
| lbi temp_reg, [mem + offset] |
| sll temp_reg, (8 * shift) |
| ior reg0, temp_reg |
| |
| lbi temp_reg, [mem + (offset + 1)] |
| sll temp_reg, (8 * (shift + 1)) |
| ior reg0, temp_reg */ |
| |
| temp_reg = gen_reg_rtx (SImode); |
| temp_sub_reg = gen_lowpart (QImode, temp_reg); |
| |
| if (mode == DImode) |
| { |
| /* Load doubleword, we need two registers to access. */ |
| reg[0] = nds32_di_low_part_subreg (operands[0]); |
| reg[1] = nds32_di_high_part_subreg (operands[0]); |
| /* A register only store 4 byte. */ |
| width = GET_MODE_SIZE (SImode) - 1; |
| } |
| else |
| { |
| if (VECTOR_MODE_P (mode)) |
| reg[0] = gen_reg_rtx (SImode); |
| else |
| reg[0] = operands[0]; |
| } |
| |
| for (num_reg = (mode == DImode) ? 2 : 1; num_reg > 0; num_reg--) |
| { |
| sub_reg = gen_lowpart (QImode, reg[0]); |
| mem[0] = gen_rtx_MEM (QImode, plus_constant (Pmode, operands[1], offset)); |
| |
| /* Generating the first part instructions. |
| lbi reg0, [mem] |
| zeh reg0, reg0 */ |
| emit_move_insn (sub_reg, mem[0]); |
| emit_insn (gen_zero_extendqisi2 (reg[0], sub_reg)); |
| |
| while (width > 0) |
| { |
| offset = offset + offset_adj; |
| shift++; |
| width--; |
| |
| mem[1] = gen_rtx_MEM (QImode, plus_constant (Pmode, |
| operands[1], |
| offset)); |
| /* Generating the second part instructions. |
| lbi temp_reg, [mem + offset] |
| sll temp_reg, (8 * shift) |
| ior reg0, temp_reg */ |
| emit_move_insn (temp_sub_reg, mem[1]); |
| emit_insn (gen_ashlsi3 (temp_reg, temp_reg, |
| GEN_INT (shift * 8))); |
| emit_insn (gen_iorsi3 (reg[0], reg[0], temp_reg)); |
| } |
| |
| if (mode == DImode) |
| { |
| /* Using the second register to load memory information. */ |
| reg[0] = reg[1]; |
| shift = 0; |
| width = GET_MODE_SIZE (SImode) - 1; |
| offset = offset + offset_adj; |
| } |
| } |
| if (VECTOR_MODE_P (mode)) |
| convert_move (operands[0], reg[0], false); |
| } |
| |
| void |
| nds32_expand_unaligned_store (rtx *operands, enum machine_mode mode) |
| { |
| /* Initial memory offset. */ |
| int offset = WORDS_BIG_ENDIAN ? GET_MODE_SIZE (mode) - 1 : 0; |
| int offset_adj = WORDS_BIG_ENDIAN ? -1 : 1; |
| /* Initial register shift byte. */ |
| int shift = 0; |
| /* The first load byte instruction is not the same. */ |
| int width = GET_MODE_SIZE (mode) - 1; |
| rtx mem[2]; |
| rtx reg[2]; |
| rtx sub_reg; |
| rtx temp_reg, temp_sub_reg; |
| int num_reg; |
| |
| /* Generating a series of store byte instructions. |
| The first store byte instructions and other |
| load byte instructions are not the same. like: |
| First: |
| sbi reg0, [mem + 0] |
| Second: |
| srli temp_reg, reg0, (8 * shift) |
| sbi temp_reg, [mem + offset] */ |
| |
| temp_reg = gen_reg_rtx (SImode); |
| temp_sub_reg = gen_lowpart (QImode, temp_reg); |
| |
| if (mode == DImode) |
| { |
| /* Load doubleword, we need two registers to access. */ |
| reg[0] = nds32_di_low_part_subreg (operands[1]); |
| reg[1] = nds32_di_high_part_subreg (operands[1]); |
| /* A register only store 4 byte. */ |
| width = GET_MODE_SIZE (SImode) - 1; |
| } |
| else |
| { |
| if (VECTOR_MODE_P (mode)) |
| { |
| reg[0] = gen_reg_rtx (SImode); |
| convert_move (reg[0], operands[1], false); |
| } |
| else |
| reg[0] = operands[1]; |
| } |
| |
| for (num_reg = (mode == DImode) ? 2 : 1; num_reg > 0; num_reg--) |
| { |
| sub_reg = gen_lowpart (QImode, reg[0]); |
| mem[0] = gen_rtx_MEM (QImode, plus_constant (Pmode, operands[0], offset)); |
| |
| /* Generating the first part instructions. |
| sbi reg0, [mem + 0] */ |
| emit_move_insn (mem[0], sub_reg); |
| |
| while (width > 0) |
| { |
| offset = offset + offset_adj; |
| shift++; |
| width--; |
| |
| mem[1] = gen_rtx_MEM (QImode, plus_constant (Pmode, |
| operands[0], |
| offset)); |
| /* Generating the second part instructions. |
| srli temp_reg, reg0, (8 * shift) |
| sbi temp_reg, [mem + offset] */ |
| emit_insn (gen_lshrsi3 (temp_reg, reg[0], |
| GEN_INT (shift * 8))); |
| emit_move_insn (mem[1], temp_sub_reg); |
| } |
| |
| if (mode == DImode) |
| { |
| /* Using the second register to load memory information. */ |
| reg[0] = reg[1]; |
| shift = 0; |
| width = GET_MODE_SIZE (SImode) - 1; |
| offset = offset + offset_adj; |
| } |
| } |
| } |
| |
| /* Using multiple load/store instruction to output doubleword instruction. */ |
| const char * |
| nds32_output_double (rtx *operands, bool load_p) |
| { |
| char pattern[100]; |
| int reg = load_p ? 0 : 1; |
| int mem = load_p ? 1 : 0; |
| rtx otherops[3]; |
| rtx addr = XEXP (operands[mem], 0); |
| |
| otherops[0] = gen_rtx_REG (SImode, REGNO (operands[reg])); |
| otherops[1] = gen_rtx_REG (SImode, REGNO (operands[reg]) + 1); |
| |
| if (GET_CODE (addr) == POST_INC) |
| { |
| /* (mem (post_inc (reg))) */ |
| otherops[2] = XEXP (addr, 0); |
| snprintf (pattern, sizeof (pattern), |
| "%cmw.bim\t%%0, [%%2], %%1, 0", load_p ? 'l' : 's'); |
| } |
| else |
| { |
| /* (mem (reg)) */ |
| otherops[2] = addr; |
| snprintf (pattern, sizeof (pattern), |
| "%cmw.bi\t%%0, [%%2], %%1, 0", load_p ? 'l' : 's'); |
| |
| } |
| |
| output_asm_insn (pattern, otherops); |
| return ""; |
| } |
| |
| const char * |
| nds32_output_cbranchsi4_equality_zero (rtx_insn *insn, rtx *operands) |
| { |
| enum rtx_code code; |
| bool long_jump_p = false; |
| |
| code = GET_CODE (operands[0]); |
| |
| /* This zero-comparison conditional branch has two forms: |
| 32-bit instruction => beqz/bnez imm16s << 1 |
| 16-bit instruction => beqzs8/bnezs8/beqz38/bnez38 imm8s << 1 |
| |
| For 32-bit case, |
| we assume it is always reachable. (but check range -65500 ~ 65500) |
| |
| For 16-bit case, |
| it must satisfy { 255 >= (label - pc) >= -256 } condition. |
| However, since the $pc for nds32 is at the beginning of the instruction, |
| we should leave some length space for current insn. |
| So we use range -250 ~ 250. */ |
| |
| switch (get_attr_length (insn)) |
| { |
| case 8: |
| long_jump_p = true; |
| /* fall through */ |
| case 2: |
| if (which_alternative == 0) |
| { |
| /* constraint: t */ |
| /* b<cond>zs8 .L0 |
| or |
| b<inverse_cond>zs8 .LCB0 |
| j .L0 |
| .LCB0: |
| */ |
| output_cond_branch_compare_zero (code, "s8", long_jump_p, |
| operands, true); |
| return ""; |
| } |
| else if (which_alternative == 1) |
| { |
| /* constraint: l */ |
| /* b<cond>z38 $r0, .L0 |
| or |
| b<inverse_cond>z38 $r0, .LCB0 |
| j .L0 |
| .LCB0: |
| */ |
| output_cond_branch_compare_zero (code, "38", long_jump_p, |
| operands, false); |
| return ""; |
| } |
| else |
| { |
| /* constraint: r */ |
| /* For which_alternative==2, it should not be here. */ |
| gcc_unreachable (); |
| } |
| case 10: |
| /* including constraints: t, l, and r */ |
| long_jump_p = true; |
| /* fall through */ |
| case 4: |
| /* including constraints: t, l, and r */ |
| output_cond_branch_compare_zero (code, "", long_jump_p, operands, false); |
| return ""; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| const char * |
| nds32_output_cbranchsi4_equality_reg (rtx_insn *insn, rtx *operands) |
| { |
| enum rtx_code code; |
| bool long_jump_p, r5_p; |
| int insn_length; |
| |
| insn_length = get_attr_length (insn); |
| |
| long_jump_p = (insn_length == 10 || insn_length == 8) ? true : false; |
| r5_p = (insn_length == 2 || insn_length == 8) ? true : false; |
| |
| code = GET_CODE (operands[0]); |
| |
| /* This register-comparison conditional branch has one form: |
| 32-bit instruction => beq/bne imm14s << 1 |
| |
| For 32-bit case, |
| we assume it is always reachable. (but check range -16350 ~ 16350). */ |
| |
| switch (code) |
| { |
| case EQ: |
| case NE: |
| output_cond_branch (code, "", r5_p, long_jump_p, operands); |
| return ""; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| const char * |
| nds32_output_cbranchsi4_equality_reg_or_const_int (rtx_insn *insn, |
| rtx *operands) |
| { |
| enum rtx_code code; |
| bool long_jump_p, r5_p; |
| int insn_length; |
| |
| insn_length = get_attr_length (insn); |
| |
| long_jump_p = (insn_length == 10 || insn_length == 8) ? true : false; |
| r5_p = (insn_length == 2 || insn_length == 8) ? true : false; |
| |
| code = GET_CODE (operands[0]); |
| |
| /* This register-comparison conditional branch has one form: |
| 32-bit instruction => beq/bne imm14s << 1 |
| 32-bit instruction => beqc/bnec imm8s << 1 |
| |
| For 32-bit case, we assume it is always reachable. |
| (but check range -16350 ~ 16350 and -250 ~ 250). */ |
| |
| switch (code) |
| { |
| case EQ: |
| case NE: |
| if (which_alternative == 2) |
| { |
| /* r, Is11 */ |
| /* b<cond>c */ |
| output_cond_branch (code, "c", r5_p, long_jump_p, operands); |
| } |
| else |
| { |
| /* r, r */ |
| /* v, r */ |
| output_cond_branch (code, "", r5_p, long_jump_p, operands); |
| } |
| return ""; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| const char * |
| nds32_output_cbranchsi4_greater_less_zero (rtx_insn *insn, rtx *operands) |
| { |
| enum rtx_code code; |
| bool long_jump_p; |
| int insn_length; |
| |
| insn_length = get_attr_length (insn); |
| |
| gcc_assert (insn_length == 4 || insn_length == 10); |
| |
| long_jump_p = (insn_length == 10) ? true : false; |
| |
| code = GET_CODE (operands[0]); |
| |
| /* This zero-greater-less-comparison conditional branch has one form: |
| 32-bit instruction => bgtz/bgez/bltz/blez imm16s << 1 |
| |
| For 32-bit case, we assume it is always reachable. |
| (but check range -65500 ~ 65500). */ |
| |
| switch (code) |
| { |
| case GT: |
| case GE: |
| case LT: |
| case LE: |
| output_cond_branch_compare_zero (code, "", long_jump_p, operands, false); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| return ""; |
| } |
| |
| const char * |
| nds32_output_unpkd8 (rtx output, rtx input, |
| rtx high_idx_rtx, rtx low_idx_rtx, |
| bool signed_p) |
| { |
| char pattern[100]; |
| rtx output_operands[2]; |
| HOST_WIDE_INT high_idx, low_idx; |
| high_idx = INTVAL (high_idx_rtx); |
| low_idx = INTVAL (low_idx_rtx); |
| |
| gcc_assert (high_idx >= 0 && high_idx <= 3); |
| gcc_assert (low_idx >= 0 && low_idx <= 3); |
| |
| /* We only have 10, 20, 30 and 31. */ |
| if ((low_idx != 0 || high_idx == 0) && |
| !(low_idx == 1 && high_idx == 3)) |
| return "#"; |
| |
| char sign_char = signed_p ? 's' : 'z'; |
| |
| sprintf (pattern, |
| "%cunpkd8" HOST_WIDE_INT_PRINT_DEC HOST_WIDE_INT_PRINT_DEC "\t%%0, %%1", |
| sign_char, high_idx, low_idx); |
| output_operands[0] = output; |
| output_operands[1] = input; |
| output_asm_insn (pattern, output_operands); |
| return ""; |
| } |
| |
| /* Return true if SYMBOL_REF X binds locally. */ |
| |
| static bool |
| nds32_symbol_binds_local_p (const_rtx x) |
| { |
| return (SYMBOL_REF_DECL (x) |
| ? targetm.binds_local_p (SYMBOL_REF_DECL (x)) |
| : SYMBOL_REF_LOCAL_P (x)); |
| } |
| |
| const char * |
| nds32_output_call (rtx insn, rtx *operands, rtx symbol, const char *long_call, |
| const char *call, bool align_p) |
| { |
| char pattern[100]; |
| bool noreturn_p; |
| |
| if (nds32_long_call_p (symbol)) |
| strcpy (pattern, long_call); |
| else |
| strcpy (pattern, call); |
| |
| if (flag_pic && CONSTANT_P (symbol) |
| && !nds32_symbol_binds_local_p (symbol)) |
| strcat (pattern, "@PLT"); |
| |
| if (align_p) |
| strcat (pattern, "\n\t.align 2"); |
| |
| noreturn_p = find_reg_note (insn, REG_NORETURN, NULL_RTX) != NULL_RTX; |
| |
| if (noreturn_p) |
| { |
| if (TARGET_16_BIT) |
| strcat (pattern, "\n\tnop16"); |
| else |
| strcat (pattern, "\n\tnop"); |
| } |
| |
| output_asm_insn (pattern, operands); |
| return ""; |
| } |
| |
| bool |
| nds32_need_split_sms_p (rtx in0_idx0, rtx in1_idx0, |
| rtx in0_idx1, rtx in1_idx1) |
| { |
| /* smds or smdrs. */ |
| if (INTVAL (in0_idx0) == INTVAL (in1_idx0) |
| && INTVAL (in0_idx1) == INTVAL (in1_idx1) |
| && INTVAL (in0_idx0) != INTVAL (in0_idx1)) |
| return false; |
| |
| /* smxds. */ |
| if (INTVAL (in0_idx0) != INTVAL (in0_idx1) |
| && INTVAL (in1_idx0) != INTVAL (in1_idx1)) |
| return false; |
| |
| return true; |
| } |
| |
| const char * |
| nds32_output_sms (rtx in0_idx0, rtx in1_idx0, |
| rtx in0_idx1, rtx in1_idx1) |
| { |
| if (nds32_need_split_sms_p (in0_idx0, in1_idx0, |
| in0_idx1, in1_idx1)) |
| return "#"; |
| /* out = in0[in0_idx0] * in1[in1_idx0] - in0[in0_idx1] * in1[in1_idx1] */ |
| |
| /* smds or smdrs. */ |
| if (INTVAL (in0_idx0) == INTVAL (in1_idx0) |
| && INTVAL (in0_idx1) == INTVAL (in1_idx1) |
| && INTVAL (in0_idx0) != INTVAL (in0_idx1)) |
| { |
| if (INTVAL (in0_idx0) == 0) |
| { |
| if (TARGET_BIG_ENDIAN) |
| return "smds\t%0, %1, %2"; |
| else |
| return "smdrs\t%0, %1, %2"; |
| } |
| else |
| { |
| if (TARGET_BIG_ENDIAN) |
| return "smdrs\t%0, %1, %2"; |
| else |
| return "smds\t%0, %1, %2"; |
| } |
| } |
| |
| if (INTVAL (in0_idx0) != INTVAL (in0_idx1) |
| && INTVAL (in1_idx0) != INTVAL (in1_idx1)) |
| { |
| if (INTVAL (in0_idx0) == 1) |
| { |
| if (TARGET_BIG_ENDIAN) |
| return "smxds\t%0, %2, %1"; |
| else |
| return "smxds\t%0, %1, %2"; |
| } |
| else |
| { |
| if (TARGET_BIG_ENDIAN) |
| return "smxds\t%0, %1, %2"; |
| else |
| return "smxds\t%0, %2, %1"; |
| } |
| } |
| |
| gcc_unreachable (); |
| return ""; |
| } |
| |
| void |
| nds32_split_sms (rtx out, rtx in0, rtx in1, |
| rtx in0_idx0, rtx in1_idx0, |
| rtx in0_idx1, rtx in1_idx1) |
| { |
| rtx result0 = gen_reg_rtx (SImode); |
| rtx result1 = gen_reg_rtx (SImode); |
| emit_insn (gen_mulhisi3v (result0, in0, in1, |
| in0_idx0, in1_idx0)); |
| emit_insn (gen_mulhisi3v (result1, in0, in1, |
| in0_idx1, in1_idx1)); |
| emit_insn (gen_subsi3 (out, result0, result1)); |
| } |
| |
| /* Spilt a doubleword instrucion to two single word instructions. */ |
| void |
| nds32_spilt_doubleword (rtx *operands, bool load_p) |
| { |
| int reg = load_p ? 0 : 1; |
| int mem = load_p ? 1 : 0; |
| rtx reg_rtx = load_p ? operands[0] : operands[1]; |
| rtx mem_rtx = load_p ? operands[1] : operands[0]; |
| rtx low_part[2], high_part[2]; |
| rtx sub_mem = XEXP (mem_rtx, 0); |
| |
| /* Generate low_part and high_part register pattern. |
| i.e. register pattern like: |
| (reg:DI) -> (subreg:SI (reg:DI)) |
| (subreg:SI (reg:DI)) */ |
| low_part[reg] = simplify_gen_subreg (SImode, reg_rtx, GET_MODE (reg_rtx), 0); |
| high_part[reg] = simplify_gen_subreg (SImode, reg_rtx, GET_MODE (reg_rtx), 4); |
| |
| /* Generate low_part and high_part memory pattern. |
| Memory format is (post_dec) will generate: |
| low_part: lwi.bi reg, [mem], 4 |
| high_part: lwi.bi reg, [mem], -12 */ |
| if (GET_CODE (sub_mem) == POST_DEC) |
| { |
| /* memory format is (post_dec (reg)), |
| so that extract (reg) from the (post_dec (reg)) pattern. */ |
| sub_mem = XEXP (sub_mem, 0); |
| |
| /* generate low_part and high_part memory format: |
| low_part: (post_modify ((reg) (plus (reg) (const 4))) |
| high_part: (post_modify ((reg) (plus (reg) (const -12))) */ |
| low_part[mem] = gen_rtx_MEM (SImode, |
| gen_rtx_POST_MODIFY (Pmode, sub_mem, |
| gen_rtx_PLUS (Pmode, |
| sub_mem, |
| GEN_INT (4)))); |
| high_part[mem] = gen_rtx_MEM (SImode, |
| gen_rtx_POST_MODIFY (Pmode, sub_mem, |
| gen_rtx_PLUS (Pmode, |
| sub_mem, |
| GEN_INT (-12)))); |
| } |
| else if (GET_CODE (sub_mem) == POST_INC) |
| { |
| /* memory format is (post_inc (reg)), |
| so that extract (reg) from the (post_inc (reg)) pattern. */ |
| sub_mem = XEXP (sub_mem, 0); |
| |
| /* generate low_part and high_part memory format: |
| low_part: (post_inc (reg)) |
| high_part: (post_inc (reg)) */ |
| low_part[mem] = gen_rtx_MEM (SImode, |
| gen_rtx_POST_INC (Pmode, sub_mem)); |
| high_part[mem] = gen_rtx_MEM (SImode, |
| gen_rtx_POST_INC (Pmode, sub_mem)); |
| } |
| else if (GET_CODE (sub_mem) == POST_MODIFY) |
| { |
| /* Memory format is (post_modify (reg) (plus (reg) (const))), |
| so that extract (reg) from the post_modify pattern. */ |
| rtx post_mem = XEXP (sub_mem, 0); |
| |
| /* Extract (const) from the (post_modify (reg) (plus (reg) (const))) |
| pattern. */ |
| |
| rtx plus_op = XEXP (sub_mem, 1); |
| rtx post_val = XEXP (plus_op, 1); |
| |
| /* Generate low_part and high_part memory format: |
| low_part: (post_modify ((reg) (plus (reg) (const))) |
| high_part: ((plus (reg) (const 4))) */ |
| low_part[mem] = gen_rtx_MEM (SImode, |
| gen_rtx_POST_MODIFY (Pmode, post_mem, |
| gen_rtx_PLUS (Pmode, |
| post_mem, |
| post_val))); |
| high_part[mem] = gen_rtx_MEM (SImode, plus_constant (Pmode, |
| post_mem, |
| 4)); |
| } |
| else |
| { |
| /* memory format: (symbol_ref), (const), (reg + const_int). */ |
| low_part[mem] = adjust_address (mem_rtx, SImode, 0); |
| high_part[mem] = adjust_address (mem_rtx, SImode, 4); |
| } |
| |
| /* After reload completed, we have dependent issue by low part register and |
| higt part memory. i.e. we cannot split a sequence |
| like: |
| load $r0, [%r1] |
| spilt to |
| lw $r0, [%r0] |
| lwi $r1, [%r0 + 4] |
| swap position |
|