| /* Subroutines used for code generation for RISC-V. |
| Copyright (C) 2023-2025 Free Software Foundation, Inc. |
| Contributed by Christoph Müllner (christoph.muellner@vrull.eu). |
| |
| 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 "target.h" |
| #include "backend.h" |
| #include "tree.h" |
| #include "rtl.h" |
| #include "insn-attr.h" |
| #include "explow.h" |
| #include "memmodel.h" |
| #include "emit-rtl.h" |
| #include "optabs.h" |
| #include "poly-int.h" |
| #include "output.h" |
| #include "regs.h" |
| #include "riscv-protos.h" |
| |
| /* If X is a PLUS of a CONST_INT, return the two terms in *BASE_PTR |
| and *OFFSET_PTR. Return X in *BASE_PTR and 0 in *OFFSET_PTR otherwise. */ |
| |
| static void |
| split_plus (rtx x, rtx *base_ptr, HOST_WIDE_INT *offset_ptr) |
| { |
| if (GET_CODE (x) == PLUS && CONST_INT_P (XEXP (x, 1))) |
| { |
| *base_ptr = XEXP (x, 0); |
| *offset_ptr = INTVAL (XEXP (x, 1)); |
| } |
| else |
| { |
| *base_ptr = x; |
| *offset_ptr = 0; |
| } |
| } |
| |
| /* Output a mempair instruction with the provided OPERANDS. |
| LOAD_P is true if a we have a pair of loads (stores otherwise). |
| MODE is the access mode (DI or SI). |
| CODE is the extension code (UNKNOWN, SIGN_EXTEND or ZERO_EXTEND). |
| This instruction does not handle invalid inputs gracefully, |
| but is full of assertions to ensure that only valid instructions |
| are emitted. */ |
| |
| const char * |
| th_mempair_output_move (rtx operands[4], bool load_p, |
| machine_mode mode, RTX_CODE code) |
| { |
| rtx reg1, reg2, mem1, mem2, base1, base2; |
| HOST_WIDE_INT offset1, offset2; |
| rtx output_operands[5]; |
| const char* format; |
| |
| gcc_assert (mode == SImode || mode == DImode); |
| |
| /* Paired 64-bit access instructions have a fixed shift amount of 4. |
| Paired 32-bit access instructions have a fixed shift amount of 3. */ |
| unsigned shamt = (mode == DImode) ? 4 : 3; |
| |
| if (load_p) |
| { |
| reg1 = copy_rtx (operands[0]); |
| reg2 = copy_rtx (operands[2]); |
| mem1 = copy_rtx (operands[1]); |
| mem2 = copy_rtx (operands[3]); |
| |
| if (mode == SImode) |
| if (code == ZERO_EXTEND) |
| format = "th.lwud\t%0, %1, (%2), %3, %4"; |
| else //SIGN_EXTEND or UNKNOWN |
| format = "th.lwd\t%0, %1, (%2), %3, %4"; |
| else |
| format = "th.ldd\t%0, %1, (%2), %3, %4"; |
| } |
| else |
| { |
| reg1 = copy_rtx (operands[1]); |
| reg2 = copy_rtx (operands[3]); |
| mem1 = copy_rtx (operands[0]); |
| mem2 = copy_rtx (operands[2]); |
| |
| if (mode == SImode) |
| format = "th.swd\t%z0, %z1, (%2), %3, %4"; |
| else |
| format = "th.sdd\t%z0, %z1, (%2), %3, %4"; |
| } |
| |
| split_plus (XEXP (mem1, 0), &base1, &offset1); |
| split_plus (XEXP (mem2, 0), &base2, &offset2); |
| gcc_assert (rtx_equal_p (base1, base2)); |
| auto size1 = MEM_SIZE (mem1); |
| auto size2 = MEM_SIZE (mem2); |
| gcc_assert (known_eq (size1, size2)); |
| gcc_assert (known_eq (offset1 + size1, offset2)); |
| |
| HOST_WIDE_INT imm2 = offset1 >> shamt; |
| |
| /* Make sure all mempair instruction constraints are met. */ |
| gcc_assert (imm2 >= 0 && imm2 < 4); |
| gcc_assert ((imm2 << shamt) == offset1); |
| gcc_assert (REG_P (reg1)); |
| gcc_assert (REG_P (reg2)); |
| gcc_assert (REG_P (base1)); |
| if (load_p) |
| { |
| gcc_assert (REGNO (reg1) != REGNO (reg2)); |
| gcc_assert (REGNO (reg1) != REGNO (base1)); |
| gcc_assert (REGNO (reg2) != REGNO (base1)); |
| } |
| |
| /* Output the mempair instruction. */ |
| output_operands[0] = copy_rtx (reg1); |
| output_operands[1] = copy_rtx (reg2); |
| output_operands[2] = copy_rtx (base1); |
| output_operands[3] = gen_rtx_CONST_INT (mode, imm2); |
| output_operands[4] = gen_rtx_CONST_INT (mode, shamt); |
| output_asm_insn (format, output_operands); |
| |
| return ""; |
| } |
| |
| /* Analyze if a pair of loads/stores MEM1 and MEM2 with given MODE |
| are consecutive so they can be merged into a mempair instruction. |
| RESERVED will be set to true, if a reversal of the accesses is |
| required (false otherwise). Returns true if the accesses can be |
| merged (even if reversing is necessary) and false if not. */ |
| |
| static bool |
| th_mempair_check_consecutive_mems (machine_mode mode, rtx *mem1, rtx *mem2, |
| bool *reversed) |
| { |
| rtx base1, base2, offset1, offset2; |
| extract_base_offset_in_addr (*mem1, &base1, &offset1); |
| extract_base_offset_in_addr (*mem2, &base2, &offset2); |
| |
| /* Make sure both mems are in base+offset form. */ |
| if (!base1 || !base2) |
| return false; |
| |
| /* If both mems use the same base register, just check the offsets. */ |
| if (rtx_equal_p (base1, base2)) |
| { |
| auto size = GET_MODE_SIZE (mode); |
| |
| if (known_eq (UINTVAL (offset1) + size, UINTVAL (offset2))) |
| { |
| *reversed = false; |
| return true; |
| } |
| |
| if (known_eq (UINTVAL (offset2) + size, UINTVAL (offset1))) |
| { |
| *reversed = true; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| return false; |
| } |
| |
| /* Check if the given MEM can be used to define the address of a mempair |
| instruction. */ |
| |
| static bool |
| th_mempair_operand_p (rtx mem, machine_mode mode) |
| { |
| if (!MEM_SIZE_KNOWN_P (mem)) |
| return false; |
| |
| /* Only DI or SI mempair instructions exist. */ |
| gcc_assert (mode == SImode || mode == DImode); |
| auto mem_sz = MEM_SIZE (mem); |
| auto mode_sz = GET_MODE_SIZE (mode); |
| if (!known_eq (mem_sz, mode_sz)) |
| return false; |
| |
| /* Paired 64-bit access instructions have a fixed shift amount of 4. |
| Paired 32-bit access instructions have a fixed shift amount of 3. */ |
| machine_mode mem_mode = GET_MODE (mem); |
| unsigned shamt = (mem_mode == DImode) ? 4 : 3; |
| |
| rtx base; |
| HOST_WIDE_INT offset; |
| split_plus (XEXP (mem, 0), &base, &offset); |
| HOST_WIDE_INT imm2 = offset >> shamt; |
| |
| if (imm2 < 0 || imm2 >= 4) |
| return false; |
| |
| if ((imm2 << shamt) != offset) |
| return false; |
| |
| return true; |
| } |
| |
| static bool |
| th_mempair_load_overlap_p (rtx reg1, rtx reg2, rtx mem) |
| { |
| if (REGNO (reg1) == REGNO (reg2)) |
| return true; |
| |
| if (reg_overlap_mentioned_p (reg1, mem)) |
| return true; |
| |
| rtx base; |
| HOST_WIDE_INT offset; |
| split_plus (XEXP (mem, 0), &base, &offset); |
| |
| if (!REG_P (base)) |
| return true; |
| |
| if (REG_P (base)) |
| { |
| if (REGNO (base) == REGNO (reg1) |
| || REGNO (base) == REGNO (reg2)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Given OPERANDS of consecutive load/store, check if we can merge |
| them into load-pair or store-pair instructions. |
| LOAD is true if they are load instructions. |
| MODE is the mode of memory operation. */ |
| |
| bool |
| th_mempair_operands_p (rtx operands[4], bool load_p, |
| machine_mode mode) |
| { |
| rtx mem_1, mem_2, reg_1, reg_2; |
| |
| if (load_p) |
| { |
| reg_1 = operands[0]; |
| mem_1 = operands[1]; |
| reg_2 = operands[2]; |
| mem_2 = operands[3]; |
| if (!REG_P (reg_1) || !REG_P (reg_2)) |
| return false; |
| if (th_mempair_load_overlap_p (reg_1, reg_2, mem_1)) |
| return false; |
| if (th_mempair_load_overlap_p (reg_1, reg_2, mem_2)) |
| return false; |
| } |
| else |
| { |
| mem_1 = operands[0]; |
| reg_1 = operands[1]; |
| mem_2 = operands[2]; |
| reg_2 = operands[3]; |
| } |
| |
| /* Check if the registers are GP registers. */ |
| if (!REG_P (reg_1) || !GP_REG_P (REGNO (reg_1)) |
| || !REG_P (reg_2) || !GP_REG_P (REGNO (reg_2))) |
| return false; |
| |
| /* The mems cannot be volatile. */ |
| if (!MEM_P (mem_1) || !MEM_P (mem_2)) |
| return false; |
| if (MEM_VOLATILE_P (mem_1) || MEM_VOLATILE_P (mem_2)) |
| return false; |
| |
| |
| /* Check if the addresses are in the form of [base+offset]. */ |
| bool reversed = false; |
| if (!th_mempair_check_consecutive_mems (mode, &mem_1, &mem_2, &reversed)) |
| return false; |
| |
| /* If necessary, reverse the local copy of the operands to simplify |
| testing of alignments and mempair operand. */ |
| if (reversed) |
| { |
| std::swap (mem_1, mem_2); |
| std::swap (reg_1, reg_2); |
| } |
| |
| /* If we have slow unaligned access, we only accept aligned memory. */ |
| if (riscv_slow_unaligned_access_p |
| && known_lt (MEM_ALIGN (mem_1), GET_MODE_SIZE (mode) * BITS_PER_UNIT)) |
| return false; |
| |
| /* The first memory accesses must be a mempair operand. */ |
| if (!th_mempair_operand_p (mem_1, mode)) |
| return false; |
| |
| /* The operands must be of the same size. */ |
| gcc_assert (known_eq (GET_MODE_SIZE (GET_MODE (mem_1)), |
| GET_MODE_SIZE (GET_MODE (mem_2)))); |
| |
| return true; |
| } |
| |
| /* Given OPERANDS of consecutive load/store that can be merged, |
| swap them if they are not in ascending order. */ |
| |
| void |
| th_mempair_order_operands (rtx operands[4], bool load_p, machine_mode mode) |
| { |
| int mem_op = load_p ? 1 : 0; |
| bool reversed = false; |
| if (!th_mempair_check_consecutive_mems (mode, |
| operands + mem_op, |
| operands + mem_op + 2, |
| &reversed)) |
| gcc_unreachable (); |
| |
| if (reversed) |
| { |
| /* Irrespective of whether this is a load or a store, |
| we do the same swap. */ |
| std::swap (operands[0], operands[2]); |
| std::swap (operands[1], operands[3]); |
| } |
| } |
| |
| /* Similar like riscv_save_reg, but saves two registers to memory |
| and marks the resulting instruction as frame-related. */ |
| |
| static void |
| th_mempair_save_regs (rtx operands[4]) |
| { |
| rtx set1 = gen_rtx_SET (operands[0], operands[1]); |
| rtx set2 = gen_rtx_SET (operands[2], operands[3]); |
| rtx dwarf = gen_rtx_SEQUENCE (VOIDmode, rtvec_alloc (2)); |
| rtx insn = emit_insn (gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, set1, set2))); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| |
| XVECEXP (dwarf, 0, 0) = copy_rtx (set1); |
| XVECEXP (dwarf, 0, 1) = copy_rtx (set2); |
| RTX_FRAME_RELATED_P (XVECEXP (dwarf, 0, 0)) = 1; |
| RTX_FRAME_RELATED_P (XVECEXP (dwarf, 0, 1)) = 1; |
| add_reg_note (insn, REG_FRAME_RELATED_EXPR, dwarf); |
| } |
| |
| /* Similar like riscv_restore_reg, but restores two registers from memory |
| and marks the instruction frame-related. */ |
| |
| static void |
| th_mempair_restore_regs (rtx operands[4]) |
| { |
| rtx set1 = gen_rtx_SET (operands[0], operands[1]); |
| rtx set2 = gen_rtx_SET (operands[2], operands[3]); |
| rtx insn = emit_insn (gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, set1, set2))); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| add_reg_note (insn, REG_CFA_RESTORE, operands[0]); |
| add_reg_note (insn, REG_CFA_RESTORE, operands[2]); |
| } |
| |
| /* Prepare the OPERANDS array to emit a mempair instruction using the |
| provided information. No checks are performed, the resulting array |
| should be validated using th_mempair_operands_p(). */ |
| |
| void |
| th_mempair_prepare_save_restore_operands (rtx operands[4], |
| bool load_p, machine_mode mode, |
| int regno, HOST_WIDE_INT offset, |
| int regno2, HOST_WIDE_INT offset2) |
| { |
| int reg_op = load_p ? 0 : 1; |
| int mem_op = load_p ? 1 : 0; |
| |
| rtx mem1 = plus_constant (mode, stack_pointer_rtx, offset); |
| mem1 = gen_frame_mem (mode, mem1); |
| rtx mem2 = plus_constant (mode, stack_pointer_rtx, offset2); |
| mem2 = gen_frame_mem (mode, mem2); |
| |
| operands[reg_op] = gen_rtx_REG (mode, regno); |
| operands[mem_op] = mem1; |
| operands[2 + reg_op] = gen_rtx_REG (mode, regno2); |
| operands[2 + mem_op] = mem2; |
| } |
| |
| /* Emit a mempair instruction to save/restore two registers to/from stack. */ |
| |
| void |
| th_mempair_save_restore_regs (rtx operands[4], bool load_p, |
| machine_mode mode) |
| { |
| gcc_assert (th_mempair_operands_p (operands, load_p, mode)); |
| |
| th_mempair_order_operands (operands, load_p, mode); |
| |
| if (load_p) |
| th_mempair_restore_regs (operands); |
| else |
| th_mempair_save_regs (operands); |
| } |
| |
| /* Return true if X can be represented as signed immediate of NBITS bits. |
| The immediate is assumed to be shifted by LSHAMT bits left. */ |
| |
| static bool |
| valid_signed_immediate (rtx x, unsigned nbits, unsigned lshamt) |
| { |
| if (GET_CODE (x) != CONST_INT) |
| return false; |
| |
| HOST_WIDE_INT v = INTVAL (x); |
| |
| HOST_WIDE_INT vunshifted = v >> lshamt; |
| |
| /* Make sure we did not shift out any bits. */ |
| if (vunshifted << lshamt != v) |
| return false; |
| |
| unsigned HOST_WIDE_INT imm_reach = 1LL << nbits; |
| return ((unsigned HOST_WIDE_INT) vunshifted + imm_reach/2 < imm_reach); |
| } |
| |
| /* Return the address RTX of a move to/from memory |
| instruction. */ |
| |
| static rtx |
| th_get_move_mem_addr (rtx dest, rtx src, bool load) |
| { |
| rtx mem; |
| |
| if (load) |
| mem = src; |
| else |
| mem = dest; |
| |
| gcc_assert (GET_CODE (mem) == MEM); |
| return XEXP (mem, 0); |
| } |
| |
| /* Return true if X is a valid address for T-Head's memory addressing modes |
| with pre/post modification for machine mode MODE. |
| If it is, fill in INFO appropriately (if non-NULL). |
| If STRICT_P is true then REG_OK_STRICT is in effect. */ |
| |
| static bool |
| th_memidx_classify_address_modify (struct riscv_address_info *info, rtx x, |
| machine_mode mode, bool strict_p) |
| { |
| if (!TARGET_XTHEADMEMIDX) |
| return false; |
| |
| if (GET_MODE_CLASS (mode) != MODE_INT |
| || GET_MODE_SIZE (mode).to_constant () > UNITS_PER_WORD) |
| return false; |
| |
| if (GET_CODE (x) != POST_MODIFY |
| && GET_CODE (x) != PRE_MODIFY) |
| return false; |
| |
| rtx reg = XEXP (x, 0); |
| rtx exp = XEXP (x, 1); |
| rtx expreg = XEXP (exp, 0); |
| rtx expoff = XEXP (exp, 1); |
| |
| if (GET_CODE (exp) != PLUS |
| || !rtx_equal_p (expreg, reg) |
| || !CONST_INT_P (expoff) |
| || !riscv_valid_base_register_p (reg, mode, strict_p)) |
| return false; |
| |
| /* The offset is calculated as (sign_extend(imm5) << imm2) */ |
| const int shamt_bits = 2; |
| for (int shamt = 0; shamt < (1 << shamt_bits); shamt++) |
| { |
| const int nbits = 5; |
| if (valid_signed_immediate (expoff, nbits, shamt)) |
| { |
| if (info) |
| { |
| info->type = ADDRESS_REG_WB; |
| info->reg = reg; |
| info->offset = expoff; |
| info->shift = shamt; |
| } |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* Return TRUE if X is a MEM with a legitimate modify address. */ |
| |
| bool |
| th_memidx_legitimate_modify_p (rtx x) |
| { |
| if (!MEM_P (x)) |
| return false; |
| |
| /* Get the mode from the MEM and unpack it. */ |
| machine_mode mode = GET_MODE (x); |
| x = XEXP (x, 0); |
| |
| return th_memidx_classify_address_modify (NULL, x, mode, reload_completed); |
| } |
| |
| /* Return TRUE if X is a MEM with a legitimate modify address |
| and the address is POST_MODIFY (if POST is true) or a PRE_MODIFY |
| (otherwise). */ |
| |
| bool |
| th_memidx_legitimate_modify_p (rtx x, bool post) |
| { |
| if (!th_memidx_legitimate_modify_p (x)) |
| return false; |
| |
| /* Unpack the MEM and check the code. */ |
| x = XEXP (x, 0); |
| if (post) |
| return GET_CODE (x) == POST_MODIFY; |
| else |
| return GET_CODE (x) == PRE_MODIFY; |
| } |
| |
| /* Provide a buffer for a th.lXia/th.lXib/th.sXia/th.sXib instruction |
| for the given MODE. If LOAD is true, a load instruction will be |
| provided (otherwise, a store instruction). If X is not suitable |
| return NULL. */ |
| |
| static const char * |
| th_memidx_output_modify (rtx dest, rtx src, machine_mode mode, bool load) |
| { |
| char format[24]; |
| rtx output_operands[2]; |
| rtx x = th_get_move_mem_addr (dest, src, load); |
| |
| /* Validate x. */ |
| if (!th_memidx_classify_address_modify (NULL, x, mode, reload_completed)) |
| return NULL; |
| |
| int index = exact_log2 (GET_MODE_SIZE (mode).to_constant ()); |
| bool post = GET_CODE (x) == POST_MODIFY; |
| |
| const char *const insn[][4] = { |
| { |
| "th.sbi%s\t%%z1,%%0", |
| "th.shi%s\t%%z1,%%0", |
| "th.swi%s\t%%z1,%%0", |
| "th.sdi%s\t%%z1,%%0" |
| }, |
| { |
| "th.lbui%s\t%%0,%%1", |
| "th.lhui%s\t%%0,%%1", |
| "th.lwi%s\t%%0,%%1", |
| "th.ldi%s\t%%0,%%1" |
| } |
| }; |
| |
| snprintf (format, sizeof (format), insn[load][index], post ? "a" : "b"); |
| output_operands[0] = dest; |
| output_operands[1] = src; |
| output_asm_insn (format, output_operands); |
| return ""; |
| } |
| |
| static bool |
| is_memidx_mode (machine_mode mode) |
| { |
| if (mode == QImode || mode == HImode || mode == SImode) |
| return true; |
| |
| if (mode == DImode && TARGET_64BIT) |
| return true; |
| |
| return false; |
| } |
| |
| static bool |
| is_fmemidx_mode (machine_mode mode) |
| { |
| if (mode == SFmode && TARGET_HARD_FLOAT) |
| return true; |
| |
| if (mode == DFmode && TARGET_DOUBLE_FLOAT) |
| return true; |
| |
| return false; |
| } |
| |
| /* Return true if X is a valid address for T-Head's memory addressing modes |
| with scaled register offsets for machine mode MODE. |
| If it is, fill in INFO appropriately (if non-NULL). |
| If STRICT_P is true then REG_OK_STRICT is in effect. */ |
| |
| static bool |
| th_memidx_classify_address_index (struct riscv_address_info *info, rtx x, |
| machine_mode mode, bool strict_p) |
| { |
| /* Ensure that the mode is supported. */ |
| if (!(TARGET_XTHEADMEMIDX && is_memidx_mode (mode)) |
| && !(TARGET_XTHEADMEMIDX |
| && TARGET_XTHEADFMEMIDX && is_fmemidx_mode (mode))) |
| return false; |
| |
| if (GET_CODE (x) != PLUS) |
| return false; |
| |
| rtx op0 = XEXP (x, 0); |
| rtx op1 = XEXP (x, 1); |
| enum riscv_address_type type; |
| int shift; |
| rtx reg = op0; |
| rtx offset = op1; |
| |
| if (!riscv_valid_base_register_p (reg, mode, strict_p)) |
| { |
| reg = op1; |
| offset = op0; |
| if (!riscv_valid_base_register_p (reg, mode, strict_p)) |
| return false; |
| } |
| |
| /* (reg:X) */ |
| if ((REG_P (offset) || SUBREG_P (offset)) |
| && GET_MODE (offset) == Xmode) |
| { |
| type = ADDRESS_REG_REG; |
| shift = 0; |
| offset = offset; |
| } |
| /* (any_extend:DI (reg:SI)) */ |
| else if (TARGET_64BIT |
| && (GET_CODE (offset) == SIGN_EXTEND |
| || GET_CODE (offset) == ZERO_EXTEND) |
| && GET_MODE (offset) == DImode |
| && GET_MODE (XEXP (offset, 0)) == SImode) |
| { |
| type = (GET_CODE (offset) == SIGN_EXTEND) |
| ? ADDRESS_REG_REG : ADDRESS_REG_UREG; |
| shift = 0; |
| offset = XEXP (offset, 0); |
| } |
| /* (mult:X (reg:X) (const_int scale)) */ |
| else if (GET_CODE (offset) == MULT |
| && GET_MODE (offset) == Xmode |
| && REG_P (XEXP (offset, 0)) |
| && GET_MODE (XEXP (offset, 0)) == Xmode |
| && CONST_INT_P (XEXP (offset, 1)) |
| && pow2p_hwi (INTVAL (XEXP (offset, 1))) |
| && IN_RANGE (exact_log2 (INTVAL (XEXP (offset, 1))), 1, 3)) |
| { |
| type = ADDRESS_REG_REG; |
| shift = exact_log2 (INTVAL (XEXP (offset, 1))); |
| offset = XEXP (offset, 0); |
| } |
| /* (mult:DI (any_extend:DI (reg:SI)) (const_int scale)) */ |
| else if (TARGET_64BIT |
| && GET_CODE (offset) == MULT |
| && GET_MODE (offset) == DImode |
| && (GET_CODE (XEXP (offset, 0)) == SIGN_EXTEND |
| || GET_CODE (XEXP (offset, 0)) == ZERO_EXTEND) |
| && GET_MODE (XEXP (offset, 0)) == DImode |
| && REG_P (XEXP (XEXP (offset, 0), 0)) |
| && GET_MODE (XEXP (XEXP (offset, 0), 0)) == SImode |
| && CONST_INT_P (XEXP (offset, 1))) |
| { |
| type = (GET_CODE (XEXP (offset, 0)) == SIGN_EXTEND) |
| ? ADDRESS_REG_REG : ADDRESS_REG_UREG; |
| shift = exact_log2 (INTVAL (XEXP (x, 1))); |
| offset = XEXP (XEXP (x, 0), 0); |
| } |
| /* (ashift:X (reg:X) (const_int shift)) */ |
| else if (GET_CODE (offset) == ASHIFT |
| && GET_MODE (offset) == Xmode |
| && REG_P (XEXP (offset, 0)) |
| && GET_MODE (XEXP (offset, 0)) == Xmode |
| && CONST_INT_P (XEXP (offset, 1)) |
| && IN_RANGE (INTVAL (XEXP (offset, 1)), 0, 3)) |
| { |
| type = ADDRESS_REG_REG; |
| shift = INTVAL (XEXP (offset, 1)); |
| offset = XEXP (offset, 0); |
| } |
| /* (ashift:DI (any_extend:DI (reg:SI)) (const_int shift)) */ |
| else if (TARGET_64BIT |
| && GET_CODE (offset) == ASHIFT |
| && GET_MODE (offset) == DImode |
| && (GET_CODE (XEXP (offset, 0)) == SIGN_EXTEND |
| || GET_CODE (XEXP (offset, 0)) == ZERO_EXTEND) |
| && GET_MODE (XEXP (offset, 0)) == DImode |
| && GET_MODE (XEXP (XEXP (offset, 0), 0)) == SImode |
| && CONST_INT_P (XEXP (offset, 1)) |
| && IN_RANGE(INTVAL (XEXP (offset, 1)), 0, 3)) |
| { |
| type = (GET_CODE (XEXP (offset, 0)) == SIGN_EXTEND) |
| ? ADDRESS_REG_REG : ADDRESS_REG_UREG; |
| shift = INTVAL (XEXP (offset, 1)); |
| offset = XEXP (XEXP (offset, 0), 0); |
| } |
| /* (and:X (mult:X (reg:X) (const_int scale)) (const_int mask)) */ |
| else if (TARGET_64BIT |
| && GET_CODE (offset) == AND |
| && GET_MODE (offset) == DImode |
| && GET_CODE (XEXP (offset, 0)) == MULT |
| && GET_MODE (XEXP (offset, 0)) == DImode |
| && REG_P (XEXP (XEXP (offset, 0), 0)) |
| && GET_MODE (XEXP (XEXP (offset, 0), 0)) == DImode |
| && CONST_INT_P (XEXP (XEXP (offset, 0), 1)) |
| && pow2p_hwi (INTVAL (XEXP (XEXP (offset, 0), 1))) |
| && IN_RANGE (exact_log2 (INTVAL (XEXP (XEXP (offset, 0), 1))), 1, 3) |
| && CONST_INT_P (XEXP (offset, 1)) |
| && INTVAL (XEXP (offset, 1)) |
| >> exact_log2 (INTVAL (XEXP (XEXP (offset, 0), 1))) == 0xffffffff) |
| { |
| type = ADDRESS_REG_UREG; |
| shift = exact_log2 (INTVAL (XEXP (XEXP (offset, 0), 1))); |
| offset = XEXP (XEXP (offset, 0), 0); |
| } |
| else |
| return false; |
| |
| if (!strict_p && SUBREG_P (offset) |
| && GET_MODE (SUBREG_REG (offset)) == SImode) |
| offset = SUBREG_REG (offset); |
| |
| if (!REG_P (offset) |
| || !riscv_regno_mode_ok_for_base_p (REGNO (offset), mode, strict_p)) |
| return false; |
| |
| if (info) |
| { |
| info->reg = reg; |
| info->type = type; |
| info->offset = offset; |
| info->shift = shift; |
| } |
| return true; |
| } |
| |
| /* Return TRUE if X is a MEM with a legitimate indexed address. */ |
| |
| bool |
| th_memidx_legitimate_index_p (rtx x) |
| { |
| if (!MEM_P (x)) |
| return false; |
| |
| /* Get the mode from the MEM and unpack it. */ |
| machine_mode mode = GET_MODE (x); |
| x = XEXP (x, 0); |
| |
| return th_memidx_classify_address_index (NULL, x, mode, reload_completed); |
| } |
| |
| /* Return TRUE if X is a MEM with a legitimate indexed address |
| and the offset register is zero-extended (if UINDEX is true) |
| or sign-extended (otherwise). */ |
| |
| bool |
| th_memidx_legitimate_index_p (rtx x, bool uindex) |
| { |
| if (!MEM_P (x)) |
| return false; |
| |
| /* Get the mode from the MEM and unpack it. */ |
| machine_mode mode = GET_MODE (x); |
| x = XEXP (x, 0); |
| |
| struct riscv_address_info info; |
| if (!th_memidx_classify_address_index (&info, x, mode, reload_completed)) |
| return false; |
| |
| if (uindex) |
| return info.type == ADDRESS_REG_UREG; |
| else |
| return info.type == ADDRESS_REG_REG; |
| } |
| |
| /* Provide a buffer for a th.lrX/th.lurX/th.srX/th.surX instruction |
| for the given MODE. If LOAD is true, a load instruction will be |
| provided (otherwise, a store instruction). If X is not suitable |
| return NULL. */ |
| |
| static const char * |
| th_memidx_output_index (rtx dest, rtx src, machine_mode mode, bool load) |
| { |
| struct riscv_address_info info; |
| char format[24]; |
| rtx output_operands[2]; |
| rtx x = th_get_move_mem_addr (dest, src, load); |
| |
| /* Validate x. */ |
| if (!th_memidx_classify_address_index (&info, x, mode, reload_completed)) |
| return NULL; |
| |
| int index = exact_log2 (GET_MODE_SIZE (mode).to_constant ()); |
| bool uindex = info.type == ADDRESS_REG_UREG; |
| |
| const char *const insn[][4] = { |
| { |
| "th.s%srb\t%%z1,%%0", |
| "th.s%srh\t%%z1,%%0", |
| "th.s%srw\t%%z1,%%0", |
| "th.s%srd\t%%z1,%%0" |
| }, |
| { |
| "th.l%srbu\t%%0,%%1", |
| "th.l%srhu\t%%0,%%1", |
| "th.l%srw\t%%0,%%1", |
| "th.l%srd\t%%0,%%1" |
| } |
| }; |
| |
| snprintf (format, sizeof (format), insn[load][index], uindex ? "u" : ""); |
| output_operands[0] = dest; |
| output_operands[1] = src; |
| output_asm_insn (format, output_operands); |
| return ""; |
| } |
| |
| /* Provide a buffer for a th.flX/th.fluX/th.fsX/th.fsuX instruction |
| for the given MODE. If LOAD is true, a load instruction will be |
| provided (otherwise, a store instruction). If X is not suitable |
| return NULL. */ |
| |
| static const char * |
| th_fmemidx_output_index (rtx dest, rtx src, machine_mode mode, bool load) |
| { |
| struct riscv_address_info info; |
| char format[24]; |
| rtx output_operands[2]; |
| rtx x = th_get_move_mem_addr (dest, src, load); |
| |
| /* Validate x. */ |
| if (!th_memidx_classify_address_index (&info, x, mode, reload_completed)) |
| return NULL; |
| |
| int index = exact_log2 (GET_MODE_SIZE (mode).to_constant ()) - 2; |
| bool uindex = info.type == ADDRESS_REG_UREG; |
| |
| const char *const insn[][2] = { |
| { |
| "th.fs%srw\t%%z1,%%0", |
| "th.fs%srd\t%%z1,%%0" |
| }, |
| { |
| "th.fl%srw\t%%0,%%1", |
| "th.fl%srd\t%%0,%%1" |
| } |
| }; |
| |
| snprintf (format, sizeof (format), insn[load][index], uindex ? "u" : ""); |
| output_operands[0] = dest; |
| output_operands[1] = src; |
| output_asm_insn (format, output_operands); |
| return ""; |
| } |
| |
| /* Return true if X is a valid address for T-Head's memory addressing modes |
| for machine mode MODE. If it is, fill in INFO appropriately (if non-NULL). |
| If STRICT_P is true then REG_OK_STRICT is in effect. */ |
| |
| bool |
| th_classify_address (struct riscv_address_info *info, rtx x, |
| machine_mode mode, bool strict_p) |
| { |
| switch (GET_CODE (x)) |
| { |
| case PLUS: |
| if (th_memidx_classify_address_index (info, x, mode, strict_p)) |
| return true; |
| break; |
| |
| case POST_MODIFY: |
| case PRE_MODIFY: |
| if (th_memidx_classify_address_modify (info, x, mode, strict_p)) |
| return true; |
| break; |
| |
| default: |
| return false; |
| } |
| |
| return false; |
| } |
| |
| /* Provide a string containing a XTheadMemIdx instruction for the given |
| MODE from the provided SRC to the provided DEST. |
| A pointer to a NULL-terminated string containing the instruction will |
| be returned if a suitable instruction is available. Otherwise, this |
| function returns NULL. */ |
| |
| const char * |
| th_output_move (rtx dest, rtx src) |
| { |
| enum rtx_code dest_code, src_code; |
| machine_mode mode; |
| const char *insn = NULL; |
| |
| dest_code = GET_CODE (dest); |
| src_code = GET_CODE (src); |
| mode = GET_MODE (dest); |
| |
| if (!(mode == GET_MODE (src) || src == CONST0_RTX (mode))) |
| return NULL; |
| |
| if (dest_code == REG && src_code == MEM) |
| { |
| if (GET_MODE_CLASS (mode) == MODE_INT |
| || (GET_MODE_CLASS (mode) == MODE_FLOAT && GP_REG_P (REGNO (dest)))) |
| { |
| if ((insn = th_memidx_output_index (dest, src, mode, true))) |
| return insn; |
| if ((insn = th_memidx_output_modify (dest, src, mode, true))) |
| return insn; |
| } |
| else if (GET_MODE_CLASS (mode) == MODE_FLOAT && HARDFP_REG_P (REGNO (dest))) |
| { |
| if ((insn = th_fmemidx_output_index (dest, src, mode, true))) |
| return insn; |
| } |
| } |
| else if (dest_code == MEM && (src_code == REG || src == CONST0_RTX (mode))) |
| { |
| if (GET_MODE_CLASS (mode) == MODE_INT |
| || src == CONST0_RTX (mode) |
| || (GET_MODE_CLASS (mode) == MODE_FLOAT && GP_REG_P (REGNO (src)))) |
| { |
| if ((insn = th_memidx_output_index (dest, src, mode, false))) |
| return insn; |
| if ((insn = th_memidx_output_modify (dest, src, mode, false))) |
| return insn; |
| } |
| else if (GET_MODE_CLASS (mode) == MODE_FLOAT && HARDFP_REG_P (REGNO (src))) |
| { |
| if ((insn = th_fmemidx_output_index (dest, src, mode, false))) |
| return insn; |
| } |
| } |
| return NULL; |
| } |
| |
| /* Define ASM_OUTPUT_OPCODE to do anything special before |
| emitting an opcode. */ |
| const char * |
| th_asm_output_opcode (FILE *asm_out_file, const char *p) |
| { |
| /* We need to add th. prefix to all the xtheadvector |
| instructions here.*/ |
| if (current_output_insn != NULL) |
| { |
| if (get_attr_type (current_output_insn) == TYPE_VSETVL) |
| { |
| if (strstr (p, "zero")) |
| { |
| if (strstr (p, "zero,zero")) |
| return "th.vsetvli\tzero,zero,e%0,%m1"; |
| else |
| return "th.vsetvli\tzero,%z0,e%1,%m2"; |
| } |
| else |
| { |
| return "th.vsetvli\t%z0,%z1,e%2,%m3"; |
| } |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VLDE || |
| get_attr_type (current_output_insn) == TYPE_VSTE || |
| get_attr_type (current_output_insn) == TYPE_VLDFF) |
| { |
| if (strstr (p, "e8") || strstr (p, "e16") || |
| strstr (p, "e32") || strstr (p, "e64")) |
| { |
| get_attr_type (current_output_insn) == TYPE_VSTE |
| ? fputs ("th.vse", asm_out_file) |
| : fputs ("th.vle", asm_out_file); |
| if (strstr (p, "e8")) |
| return p+4; |
| else |
| return p+5; |
| } |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VLDS || |
| get_attr_type (current_output_insn) == TYPE_VSTS) |
| { |
| if (strstr (p, "vle8") || strstr (p, "vse8") || |
| strstr (p, "vle16") || strstr (p, "vse16") || |
| strstr (p, "vle32") || strstr (p, "vse32") || |
| strstr (p, "vle64") || strstr (p, "vse64")) |
| { |
| get_attr_type (current_output_insn) == TYPE_VSTS |
| ? fputs ("th.vse", asm_out_file) |
| : fputs ("th.vle", asm_out_file); |
| if (strstr (p, "e8")) |
| return p+4; |
| else |
| return p+5; |
| } |
| else if (strstr (p, "vlse8") || strstr (p, "vsse8") || |
| strstr (p, "vlse16") || strstr (p, "vsse16") || |
| strstr (p, "vlse32") || strstr (p, "vsse32") || |
| strstr (p, "vlse64") || strstr (p, "vsse64")) |
| { |
| get_attr_type (current_output_insn) == TYPE_VSTS |
| ? fputs ("th.vsse", asm_out_file) |
| : fputs ("th.vlse", asm_out_file); |
| if (strstr (p, "e8")) |
| return p+5; |
| else |
| return p+6; |
| } |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VLDUX || |
| get_attr_type (current_output_insn) == TYPE_VLDOX) |
| { |
| if (strstr (p, "ei")) |
| { |
| fputs ("th.vlxe", asm_out_file); |
| if (strstr (p, "ei8")) |
| return p+7; |
| else |
| return p+8; |
| } |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VSTUX || |
| get_attr_type (current_output_insn) == TYPE_VSTOX) |
| { |
| if (strstr (p, "ei")) |
| { |
| get_attr_type (current_output_insn) == TYPE_VSTUX |
| ? fputs ("th.vsuxe", asm_out_file) |
| : fputs ("th.vsxe", asm_out_file); |
| if (strstr (p, "ei8")) |
| return p+7; |
| else |
| return p+8; |
| } |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VLSEGDE || |
| get_attr_type (current_output_insn) == TYPE_VSSEGTE || |
| get_attr_type (current_output_insn) == TYPE_VLSEGDFF) |
| { |
| get_attr_type (current_output_insn) == TYPE_VSSEGTE |
| ? fputs ("th.vsseg", asm_out_file) |
| : fputs ("th.vlseg", asm_out_file); |
| asm_fprintf (asm_out_file, "%c", p[5]); |
| fputs ("e", asm_out_file); |
| if (strstr (p, "e8")) |
| return p+8; |
| else |
| return p+9; |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VLSEGDS || |
| get_attr_type (current_output_insn) == TYPE_VSSEGTS) |
| { |
| get_attr_type (current_output_insn) == TYPE_VSSEGTS |
| ? fputs ("th.vssseg", asm_out_file) |
| : fputs ("th.vlsseg", asm_out_file); |
| asm_fprintf (asm_out_file, "%c", p[6]); |
| fputs ("e", asm_out_file); |
| if (strstr (p, "e8")) |
| return p+9; |
| else |
| return p+10; |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VLSEGDUX || |
| get_attr_type (current_output_insn) == TYPE_VLSEGDOX) |
| { |
| fputs ("th.vlxseg", asm_out_file); |
| asm_fprintf (asm_out_file, "%c", p[7]); |
| fputs ("e", asm_out_file); |
| if (strstr (p, "ei8")) |
| return p+11; |
| else |
| return p+12; |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VSSEGTUX || |
| get_attr_type (current_output_insn) == TYPE_VSSEGTOX) |
| { |
| fputs ("th.vsxseg", asm_out_file); |
| asm_fprintf (asm_out_file, "%c", p[7]); |
| fputs ("e", asm_out_file); |
| if (strstr (p, "ei8")) |
| return p+11; |
| else |
| return p+12; |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VNSHIFT) |
| { |
| if (strstr (p, "vncvt")) |
| { |
| fputs ("th.vncvt.x.x.v", asm_out_file); |
| return p+11; |
| } |
| |
| strstr (p, "vnsrl") ? fputs ("th.vnsrl.v", asm_out_file) |
| : fputs ("th.vnsra.v", asm_out_file); |
| return p+7; |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VNCLIP) |
| { |
| if (strstr (p, "vnclipu")) |
| { |
| fputs ("th.vnclipu.v", asm_out_file); |
| return p+9; |
| } |
| else |
| { |
| fputs ("th.vnclip.v", asm_out_file); |
| return p+8; |
| } |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VMPOP) |
| { |
| fputs ("th.vmpopc", asm_out_file); |
| return p+5; |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VMFFS) |
| { |
| fputs ("th.vmfirst", asm_out_file); |
| return p+6; |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VFNCVTFTOI || |
| get_attr_type (current_output_insn) == TYPE_VFNCVTITOF) |
| { |
| if (strstr (p, "xu")) |
| { |
| get_attr_type (current_output_insn) == TYPE_VFNCVTFTOI |
| ? fputs ("th.vfncvt.xu.f.v", asm_out_file) |
| : fputs ("th.vfncvt.f.xu.v", asm_out_file); |
| return p+13; |
| } |
| else |
| { |
| get_attr_type (current_output_insn) == TYPE_VFNCVTFTOI |
| ? fputs ("th.vfncvt.x.f.v", asm_out_file) |
| : fputs ("th.vfncvt.f.x.v", asm_out_file); |
| return p+12; |
| } |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VFNCVTFTOF) |
| { |
| fputs ("th.vfncvt.f.f.v", asm_out_file); |
| return p+12; |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VFREDU |
| && strstr (p, "sum")) |
| { |
| fputs ("th.vfredsum", asm_out_file); |
| return p+9; |
| } |
| |
| if (get_attr_type (current_output_insn) == TYPE_VFWREDU |
| && strstr (p, "sum")) |
| { |
| fputs ("th.vfwredsum", asm_out_file); |
| return p+10; |
| } |
| |
| if (p[0] == 'v') |
| fputs ("th.", asm_out_file); |
| } |
| |
| return p; |
| } |
| |
| /* Implement TARGET_PRINT_OPERAND_ADDRESS for XTheadMemIdx. */ |
| |
| bool |
| th_print_operand_address (FILE *file, machine_mode mode, rtx x) |
| { |
| struct riscv_address_info addr; |
| |
| if (!th_classify_address (&addr, x, mode, reload_completed)) |
| return false; |
| |
| switch (addr.type) |
| { |
| case ADDRESS_REG_REG: |
| case ADDRESS_REG_UREG: |
| fprintf (file, "%s,%s,%u", reg_names[REGNO (addr.reg)], |
| reg_names[REGNO (addr.offset)], addr.shift); |
| return true; |
| |
| case ADDRESS_REG_WB: |
| fprintf (file, "(%s)," HOST_WIDE_INT_PRINT_DEC ",%u", |
| reg_names[REGNO (addr.reg)], |
| INTVAL (addr.offset) >> addr.shift, addr.shift); |
| return true; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| gcc_unreachable (); |
| } |
| |
| /* Number array of registers X1, X5-X7, X10-X17, X28-X31, to be |
| operated on by instruction th.ipush/th.ipop in XTheadInt. */ |
| |
| int th_int_regs[] ={ |
| RETURN_ADDR_REGNUM, |
| T0_REGNUM, T1_REGNUM, T2_REGNUM, |
| A0_REGNUM, A1_REGNUM, A2_REGNUM, A3_REGNUM, |
| A4_REGNUM, A5_REGNUM, A6_REGNUM, A7_REGNUM, |
| T3_REGNUM, T4_REGNUM, T5_REGNUM, T6_REGNUM, |
| }; |
| |
| /* If MASK contains registers X1, X5-X7, X10-X17, X28-X31, then |
| return the mask composed of these registers, otherwise return |
| zero. */ |
| |
| unsigned int |
| th_int_get_mask (unsigned int mask) |
| { |
| unsigned int xtheadint_mask = 0; |
| |
| if (!TARGET_XTHEADINT || TARGET_64BIT) |
| return 0; |
| |
| for (unsigned int i = 0; i < ARRAY_SIZE (th_int_regs); i++) |
| { |
| if (!BITSET_P (mask, th_int_regs[i])) |
| return 0; |
| |
| xtheadint_mask |= (1 << th_int_regs[i]); |
| } |
| |
| return xtheadint_mask; /* Usually 0xf003fce2. */ |
| } |
| |
| /* Returns the occupied frame needed to save registers X1, X5-X7, |
| X10-X17, X28-X31. */ |
| |
| unsigned int |
| th_int_get_save_adjustment (void) |
| { |
| gcc_assert (TARGET_XTHEADINT && !TARGET_64BIT); |
| return ARRAY_SIZE (th_int_regs) * UNITS_PER_WORD; |
| } |
| |
| rtx |
| th_int_adjust_cfi_prologue (unsigned int mask) |
| { |
| gcc_assert (TARGET_XTHEADINT && !TARGET_64BIT); |
| |
| rtx dwarf = NULL_RTX; |
| rtx adjust_sp_rtx, reg, mem, insn; |
| int saved_size = ARRAY_SIZE (th_int_regs) * UNITS_PER_WORD; |
| int offset = saved_size; |
| |
| for (int regno = GP_REG_FIRST; regno <= GP_REG_LAST; regno++) |
| if (BITSET_P (mask, regno - GP_REG_FIRST)) |
| { |
| offset -= UNITS_PER_WORD; |
| reg = gen_rtx_REG (SImode, regno); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| |
| insn = gen_rtx_SET (mem, reg); |
| dwarf = alloc_reg_note (REG_CFA_OFFSET, insn, dwarf); |
| } |
| |
| /* Debug info for adjust sp. */ |
| adjust_sp_rtx = |
| gen_rtx_SET (stack_pointer_rtx, |
| gen_rtx_PLUS (GET_MODE (stack_pointer_rtx), |
| stack_pointer_rtx, GEN_INT (-saved_size))); |
| dwarf = alloc_reg_note (REG_CFA_ADJUST_CFA, adjust_sp_rtx, dwarf); |
| |
| return dwarf; |
| } |