| /* Subroutines used for code generation for RISC-V. |
| Copyright (C) 2011-2021 Free Software Foundation, Inc. |
| Contributed by Andrew Waterman (andrew@sifive.com). |
| Based on MIPS target for GNU compiler. |
| |
| 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 |
| |
| #define INCLUDE_STRING |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include "rtl.h" |
| #include "regs.h" |
| #include "insn-config.h" |
| #include "insn-attr.h" |
| #include "recog.h" |
| #include "output.h" |
| #include "alias.h" |
| #include "tree.h" |
| #include "stringpool.h" |
| #include "attribs.h" |
| #include "varasm.h" |
| #include "stor-layout.h" |
| #include "calls.h" |
| #include "function.h" |
| #include "explow.h" |
| #include "memmodel.h" |
| #include "emit-rtl.h" |
| #include "reload.h" |
| #include "tm_p.h" |
| #include "target.h" |
| #include "target-def.h" |
| #include "basic-block.h" |
| #include "expr.h" |
| #include "optabs.h" |
| #include "bitmap.h" |
| #include "df.h" |
| #include "diagnostic.h" |
| #include "builtins.h" |
| #include "predict.h" |
| #include "tree-pass.h" |
| |
| /* True if X is an UNSPEC wrapper around a SYMBOL_REF or LABEL_REF. */ |
| #define UNSPEC_ADDRESS_P(X) \ |
| (GET_CODE (X) == UNSPEC \ |
| && XINT (X, 1) >= UNSPEC_ADDRESS_FIRST \ |
| && XINT (X, 1) < UNSPEC_ADDRESS_FIRST + NUM_SYMBOL_TYPES) |
| |
| /* Extract the symbol or label from UNSPEC wrapper X. */ |
| #define UNSPEC_ADDRESS(X) \ |
| XVECEXP (X, 0, 0) |
| |
| /* Extract the symbol type from UNSPEC wrapper X. */ |
| #define UNSPEC_ADDRESS_TYPE(X) \ |
| ((enum riscv_symbol_type) (XINT (X, 1) - UNSPEC_ADDRESS_FIRST)) |
| |
| /* True if bit BIT is set in VALUE. */ |
| #define BITSET_P(VALUE, BIT) (((VALUE) & (1ULL << (BIT))) != 0) |
| |
| /* Classifies an address. |
| |
| ADDRESS_REG |
| A natural register + offset address. The register satisfies |
| riscv_valid_base_register_p and the offset is a const_arith_operand. |
| |
| ADDRESS_LO_SUM |
| A LO_SUM rtx. The first operand is a valid base register and |
| the second operand is a symbolic address. |
| |
| ADDRESS_CONST_INT |
| A signed 16-bit constant address. |
| |
| ADDRESS_SYMBOLIC: |
| A constant symbolic address. */ |
| enum riscv_address_type { |
| ADDRESS_REG, |
| ADDRESS_LO_SUM, |
| ADDRESS_CONST_INT, |
| ADDRESS_SYMBOLIC |
| }; |
| |
| /* Information about a function's frame layout. */ |
| struct GTY(()) riscv_frame_info { |
| /* The size of the frame in bytes. */ |
| HOST_WIDE_INT total_size; |
| |
| /* Bit X is set if the function saves or restores GPR X. */ |
| unsigned int mask; |
| |
| /* Likewise FPR X. */ |
| unsigned int fmask; |
| |
| /* How much the GPR save/restore routines adjust sp (or 0 if unused). */ |
| unsigned save_libcall_adjustment; |
| |
| /* Offsets of fixed-point and floating-point save areas from frame bottom */ |
| HOST_WIDE_INT gp_sp_offset; |
| HOST_WIDE_INT fp_sp_offset; |
| |
| /* Offset of virtual frame pointer from stack pointer/frame bottom */ |
| HOST_WIDE_INT frame_pointer_offset; |
| |
| /* Offset of hard frame pointer from stack pointer/frame bottom */ |
| HOST_WIDE_INT hard_frame_pointer_offset; |
| |
| /* The offset of arg_pointer_rtx from the bottom of the frame. */ |
| HOST_WIDE_INT arg_pointer_offset; |
| }; |
| |
| enum riscv_privilege_levels { |
| UNKNOWN_MODE, USER_MODE, SUPERVISOR_MODE, MACHINE_MODE |
| }; |
| |
| struct GTY(()) machine_function { |
| /* The number of extra stack bytes taken up by register varargs. |
| This area is allocated by the callee at the very top of the frame. */ |
| int varargs_size; |
| |
| /* True if current function is a naked function. */ |
| bool naked_p; |
| |
| /* True if current function is an interrupt function. */ |
| bool interrupt_handler_p; |
| /* For an interrupt handler, indicates the privilege level. */ |
| enum riscv_privilege_levels interrupt_mode; |
| |
| /* True if attributes on current function have been checked. */ |
| bool attributes_checked_p; |
| |
| /* The current frame information, calculated by riscv_compute_frame_info. */ |
| struct riscv_frame_info frame; |
| }; |
| |
| /* Information about a single argument. */ |
| struct riscv_arg_info { |
| /* True if the argument is at least partially passed on the stack. */ |
| bool stack_p; |
| |
| /* The number of integer registers allocated to this argument. */ |
| unsigned int num_gprs; |
| |
| /* The offset of the first register used, provided num_gprs is nonzero. |
| If passed entirely on the stack, the value is MAX_ARGS_IN_REGISTERS. */ |
| unsigned int gpr_offset; |
| |
| /* The number of floating-point registers allocated to this argument. */ |
| unsigned int num_fprs; |
| |
| /* The offset of the first register used, provided num_fprs is nonzero. */ |
| unsigned int fpr_offset; |
| }; |
| |
| /* Information about an address described by riscv_address_type. |
| |
| ADDRESS_CONST_INT |
| No fields are used. |
| |
| ADDRESS_REG |
| REG is the base register and OFFSET is the constant offset. |
| |
| ADDRESS_LO_SUM |
| REG and OFFSET are the operands to the LO_SUM and SYMBOL_TYPE |
| is the type of symbol it references. |
| |
| ADDRESS_SYMBOLIC |
| SYMBOL_TYPE is the type of symbol that the address references. */ |
| struct riscv_address_info { |
| enum riscv_address_type type; |
| rtx reg; |
| rtx offset; |
| enum riscv_symbol_type symbol_type; |
| }; |
| |
| /* One stage in a constant building sequence. These sequences have |
| the form: |
| |
| A = VALUE[0] |
| A = A CODE[1] VALUE[1] |
| A = A CODE[2] VALUE[2] |
| ... |
| |
| where A is an accumulator, each CODE[i] is a binary rtl operation |
| and each VALUE[i] is a constant integer. CODE[0] is undefined. */ |
| struct riscv_integer_op { |
| enum rtx_code code; |
| unsigned HOST_WIDE_INT value; |
| }; |
| |
| /* The largest number of operations needed to load an integer constant. |
| The worst case is LUI, ADDI, SLLI, ADDI, SLLI, ADDI, SLLI, ADDI. */ |
| #define RISCV_MAX_INTEGER_OPS 8 |
| |
| /* Costs of various operations on the different architectures. */ |
| |
| struct riscv_tune_param |
| { |
| unsigned short fp_add[2]; |
| unsigned short fp_mul[2]; |
| unsigned short fp_div[2]; |
| unsigned short int_mul[2]; |
| unsigned short int_div[2]; |
| unsigned short issue_rate; |
| unsigned short branch_cost; |
| unsigned short memory_cost; |
| bool slow_unaligned_access; |
| }; |
| |
| /* Information about one micro-arch we know about. */ |
| struct riscv_tune_info { |
| /* This micro-arch canonical name. */ |
| const char *name; |
| |
| /* Which automaton to use for tuning. */ |
| enum riscv_microarchitecture_type microarchitecture; |
| |
| /* Tuning parameters for this micro-arch. */ |
| const struct riscv_tune_param *tune_param; |
| }; |
| |
| /* Global variables for machine-dependent things. */ |
| |
| /* Whether unaligned accesses execute very slowly. */ |
| bool riscv_slow_unaligned_access_p; |
| |
| /* Stack alignment to assume/maintain. */ |
| unsigned riscv_stack_boundary; |
| |
| /* If non-zero, this is an offset to be added to SP to redefine the CFA |
| when restoring the FP register from the stack. Only valid when generating |
| the epilogue. */ |
| static int epilogue_cfa_sp_offset; |
| |
| /* Which tuning parameters to use. */ |
| static const struct riscv_tune_param *tune_param; |
| |
| /* Which automaton to use for tuning. */ |
| enum riscv_microarchitecture_type riscv_microarchitecture; |
| |
| /* Index R is the smallest register class that contains register R. */ |
| const enum reg_class riscv_regno_to_class[FIRST_PSEUDO_REGISTER] = { |
| GR_REGS, GR_REGS, GR_REGS, GR_REGS, |
| GR_REGS, GR_REGS, SIBCALL_REGS, SIBCALL_REGS, |
| JALR_REGS, JALR_REGS, SIBCALL_REGS, SIBCALL_REGS, |
| SIBCALL_REGS, SIBCALL_REGS, SIBCALL_REGS, SIBCALL_REGS, |
| SIBCALL_REGS, SIBCALL_REGS, JALR_REGS, JALR_REGS, |
| JALR_REGS, JALR_REGS, JALR_REGS, JALR_REGS, |
| JALR_REGS, JALR_REGS, JALR_REGS, JALR_REGS, |
| SIBCALL_REGS, SIBCALL_REGS, SIBCALL_REGS, SIBCALL_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FRAME_REGS, FRAME_REGS, |
| }; |
| |
| /* Costs to use when optimizing for rocket. */ |
| static const struct riscv_tune_param rocket_tune_info = { |
| {COSTS_N_INSNS (4), COSTS_N_INSNS (5)}, /* fp_add */ |
| {COSTS_N_INSNS (4), COSTS_N_INSNS (5)}, /* fp_mul */ |
| {COSTS_N_INSNS (20), COSTS_N_INSNS (20)}, /* fp_div */ |
| {COSTS_N_INSNS (4), COSTS_N_INSNS (4)}, /* int_mul */ |
| {COSTS_N_INSNS (6), COSTS_N_INSNS (6)}, /* int_div */ |
| 1, /* issue_rate */ |
| 3, /* branch_cost */ |
| 5, /* memory_cost */ |
| true, /* slow_unaligned_access */ |
| }; |
| |
| /* Costs to use when optimizing for Sifive 7 Series. */ |
| static const struct riscv_tune_param sifive_7_tune_info = { |
| {COSTS_N_INSNS (4), COSTS_N_INSNS (5)}, /* fp_add */ |
| {COSTS_N_INSNS (4), COSTS_N_INSNS (5)}, /* fp_mul */ |
| {COSTS_N_INSNS (20), COSTS_N_INSNS (20)}, /* fp_div */ |
| {COSTS_N_INSNS (4), COSTS_N_INSNS (4)}, /* int_mul */ |
| {COSTS_N_INSNS (6), COSTS_N_INSNS (6)}, /* int_div */ |
| 2, /* issue_rate */ |
| 4, /* branch_cost */ |
| 3, /* memory_cost */ |
| true, /* slow_unaligned_access */ |
| }; |
| |
| /* Costs to use when optimizing for T-HEAD c906. */ |
| static const struct riscv_tune_param thead_c906_tune_info = { |
| {COSTS_N_INSNS (4), COSTS_N_INSNS (5)}, /* fp_add */ |
| {COSTS_N_INSNS (4), COSTS_N_INSNS (5)}, /* fp_mul */ |
| {COSTS_N_INSNS (20), COSTS_N_INSNS (20)}, /* fp_div */ |
| {COSTS_N_INSNS (4), COSTS_N_INSNS (4)}, /* int_mul */ |
| {COSTS_N_INSNS (6), COSTS_N_INSNS (6)}, /* int_div */ |
| 1, /* issue_rate */ |
| 3, /* branch_cost */ |
| 5, /* memory_cost */ |
| false, /* slow_unaligned_access */ |
| }; |
| |
| /* Costs to use when optimizing for size. */ |
| static const struct riscv_tune_param optimize_size_tune_info = { |
| {COSTS_N_INSNS (1), COSTS_N_INSNS (1)}, /* fp_add */ |
| {COSTS_N_INSNS (1), COSTS_N_INSNS (1)}, /* fp_mul */ |
| {COSTS_N_INSNS (1), COSTS_N_INSNS (1)}, /* fp_div */ |
| {COSTS_N_INSNS (1), COSTS_N_INSNS (1)}, /* int_mul */ |
| {COSTS_N_INSNS (1), COSTS_N_INSNS (1)}, /* int_div */ |
| 1, /* issue_rate */ |
| 1, /* branch_cost */ |
| 2, /* memory_cost */ |
| false, /* slow_unaligned_access */ |
| }; |
| |
| static tree riscv_handle_fndecl_attribute (tree *, tree, tree, int, bool *); |
| static tree riscv_handle_type_attribute (tree *, tree, tree, int, bool *); |
| |
| /* Defining target-specific uses of __attribute__. */ |
| static const struct attribute_spec riscv_attribute_table[] = |
| { |
| /* Syntax: { name, min_len, max_len, decl_required, type_required, |
| function_type_required, affects_type_identity, handler, |
| exclude } */ |
| |
| /* The attribute telling no prologue/epilogue. */ |
| { "naked", 0, 0, true, false, false, false, |
| riscv_handle_fndecl_attribute, NULL }, |
| /* This attribute generates prologue/epilogue for interrupt handlers. */ |
| { "interrupt", 0, 1, false, true, true, false, |
| riscv_handle_type_attribute, NULL }, |
| |
| /* The last attribute spec is set to be NULL. */ |
| { NULL, 0, 0, false, false, false, false, NULL, NULL } |
| }; |
| |
| /* Order for the CLOBBERs/USEs of gpr_save. */ |
| static const unsigned gpr_save_reg_order[] = { |
| INVALID_REGNUM, T0_REGNUM, T1_REGNUM, RETURN_ADDR_REGNUM, |
| S0_REGNUM, S1_REGNUM, S2_REGNUM, S3_REGNUM, S4_REGNUM, |
| S5_REGNUM, S6_REGNUM, S7_REGNUM, S8_REGNUM, S9_REGNUM, |
| S10_REGNUM, S11_REGNUM |
| }; |
| |
| /* A table describing all the processors GCC knows about. */ |
| static const struct riscv_tune_info riscv_tune_info_table[] = { |
| { "rocket", generic, &rocket_tune_info }, |
| { "sifive-3-series", generic, &rocket_tune_info }, |
| { "sifive-5-series", generic, &rocket_tune_info }, |
| { "sifive-7-series", sifive_7, &sifive_7_tune_info }, |
| { "thead-c906", generic, &thead_c906_tune_info }, |
| { "size", generic, &optimize_size_tune_info }, |
| }; |
| |
| /* Implement TARGET_MIN_ARITHMETIC_PRECISION. */ |
| |
| static unsigned int |
| riscv_min_arithmetic_precision (void) |
| { |
| return 32; |
| } |
| |
| /* Return the riscv_tune_info entry for the given name string. */ |
| |
| static const struct riscv_tune_info * |
| riscv_parse_tune (const char *tune_string) |
| { |
| const riscv_cpu_info *cpu = riscv_find_cpu (tune_string); |
| |
| if (cpu) |
| tune_string = cpu->tune; |
| |
| for (unsigned i = 0; i < ARRAY_SIZE (riscv_tune_info_table); i++) |
| if (strcmp (riscv_tune_info_table[i].name, tune_string) == 0) |
| return riscv_tune_info_table + i; |
| |
| error ("unknown cpu %qs for %<-mtune%>", tune_string); |
| return riscv_tune_info_table; |
| } |
| |
| /* Helper function for riscv_build_integer; arguments are as for |
| riscv_build_integer. */ |
| |
| static int |
| riscv_build_integer_1 (struct riscv_integer_op codes[RISCV_MAX_INTEGER_OPS], |
| HOST_WIDE_INT value, machine_mode mode) |
| { |
| HOST_WIDE_INT low_part = CONST_LOW_PART (value); |
| int cost = RISCV_MAX_INTEGER_OPS + 1, alt_cost; |
| struct riscv_integer_op alt_codes[RISCV_MAX_INTEGER_OPS]; |
| |
| if (SMALL_OPERAND (value) || LUI_OPERAND (value)) |
| { |
| /* Simply ADDI or LUI. */ |
| codes[0].code = UNKNOWN; |
| codes[0].value = value; |
| return 1; |
| } |
| |
| /* End with ADDI. When constructing HImode constants, do not generate any |
| intermediate value that is not itself a valid HImode constant. The |
| XORI case below will handle those remaining HImode constants. */ |
| if (low_part != 0 |
| && (mode != HImode |
| || value - low_part <= ((1 << (GET_MODE_BITSIZE (HImode) - 1)) - 1))) |
| { |
| alt_cost = 1 + riscv_build_integer_1 (alt_codes, value - low_part, mode); |
| if (alt_cost < cost) |
| { |
| alt_codes[alt_cost-1].code = PLUS; |
| alt_codes[alt_cost-1].value = low_part; |
| memcpy (codes, alt_codes, sizeof (alt_codes)); |
| cost = alt_cost; |
| } |
| } |
| |
| /* End with XORI. */ |
| if (cost > 2 && (low_part < 0 || mode == HImode)) |
| { |
| alt_cost = 1 + riscv_build_integer_1 (alt_codes, value ^ low_part, mode); |
| if (alt_cost < cost) |
| { |
| alt_codes[alt_cost-1].code = XOR; |
| alt_codes[alt_cost-1].value = low_part; |
| memcpy (codes, alt_codes, sizeof (alt_codes)); |
| cost = alt_cost; |
| } |
| } |
| |
| /* Eliminate trailing zeros and end with SLLI. */ |
| if (cost > 2 && (value & 1) == 0) |
| { |
| int shift = ctz_hwi (value); |
| unsigned HOST_WIDE_INT x = value; |
| x = sext_hwi (x >> shift, HOST_BITS_PER_WIDE_INT - shift); |
| |
| /* Don't eliminate the lower 12 bits if LUI might apply. */ |
| if (shift > IMM_BITS && !SMALL_OPERAND (x) && LUI_OPERAND (x << IMM_BITS)) |
| shift -= IMM_BITS, x <<= IMM_BITS; |
| |
| alt_cost = 1 + riscv_build_integer_1 (alt_codes, x, mode); |
| if (alt_cost < cost) |
| { |
| alt_codes[alt_cost-1].code = ASHIFT; |
| alt_codes[alt_cost-1].value = shift; |
| memcpy (codes, alt_codes, sizeof (alt_codes)); |
| cost = alt_cost; |
| } |
| } |
| |
| gcc_assert (cost <= RISCV_MAX_INTEGER_OPS); |
| return cost; |
| } |
| |
| /* Fill CODES with a sequence of rtl operations to load VALUE. |
| Return the number of operations needed. */ |
| |
| static int |
| riscv_build_integer (struct riscv_integer_op *codes, HOST_WIDE_INT value, |
| machine_mode mode) |
| { |
| int cost = riscv_build_integer_1 (codes, value, mode); |
| |
| /* Eliminate leading zeros and end with SRLI. */ |
| if (value > 0 && cost > 2) |
| { |
| struct riscv_integer_op alt_codes[RISCV_MAX_INTEGER_OPS]; |
| int alt_cost, shift = clz_hwi (value); |
| HOST_WIDE_INT shifted_val; |
| |
| /* Try filling trailing bits with 1s. */ |
| shifted_val = (value << shift) | ((((HOST_WIDE_INT) 1) << shift) - 1); |
| alt_cost = 1 + riscv_build_integer_1 (alt_codes, shifted_val, mode); |
| if (alt_cost < cost) |
| { |
| alt_codes[alt_cost-1].code = LSHIFTRT; |
| alt_codes[alt_cost-1].value = shift; |
| memcpy (codes, alt_codes, sizeof (alt_codes)); |
| cost = alt_cost; |
| } |
| |
| /* Try filling trailing bits with 0s. */ |
| shifted_val = value << shift; |
| alt_cost = 1 + riscv_build_integer_1 (alt_codes, shifted_val, mode); |
| if (alt_cost < cost) |
| { |
| alt_codes[alt_cost-1].code = LSHIFTRT; |
| alt_codes[alt_cost-1].value = shift; |
| memcpy (codes, alt_codes, sizeof (alt_codes)); |
| cost = alt_cost; |
| } |
| } |
| |
| return cost; |
| } |
| |
| /* Return the cost of constructing VAL in the event that a scratch |
| register is available. */ |
| |
| static int |
| riscv_split_integer_cost (HOST_WIDE_INT val) |
| { |
| int cost; |
| unsigned HOST_WIDE_INT loval = sext_hwi (val, 32); |
| unsigned HOST_WIDE_INT hival = sext_hwi ((val - loval) >> 32, 32); |
| struct riscv_integer_op codes[RISCV_MAX_INTEGER_OPS]; |
| |
| cost = 2 + riscv_build_integer (codes, loval, VOIDmode); |
| if (loval != hival) |
| cost += riscv_build_integer (codes, hival, VOIDmode); |
| |
| return cost; |
| } |
| |
| /* Return the cost of constructing the integer constant VAL. */ |
| |
| static int |
| riscv_integer_cost (HOST_WIDE_INT val) |
| { |
| struct riscv_integer_op codes[RISCV_MAX_INTEGER_OPS]; |
| return MIN (riscv_build_integer (codes, val, VOIDmode), |
| riscv_split_integer_cost (val)); |
| } |
| |
| /* Try to split a 64b integer into 32b parts, then reassemble. */ |
| |
| static rtx |
| riscv_split_integer (HOST_WIDE_INT val, machine_mode mode) |
| { |
| unsigned HOST_WIDE_INT loval = sext_hwi (val, 32); |
| unsigned HOST_WIDE_INT hival = sext_hwi ((val - loval) >> 32, 32); |
| rtx hi = gen_reg_rtx (mode), lo = gen_reg_rtx (mode); |
| |
| riscv_move_integer (hi, hi, hival, mode, FALSE); |
| riscv_move_integer (lo, lo, loval, mode, FALSE); |
| |
| hi = gen_rtx_fmt_ee (ASHIFT, mode, hi, GEN_INT (32)); |
| hi = force_reg (mode, hi); |
| |
| return gen_rtx_fmt_ee (PLUS, mode, hi, lo); |
| } |
| |
| /* Return true if X is a thread-local symbol. */ |
| |
| static bool |
| riscv_tls_symbol_p (const_rtx x) |
| { |
| return SYMBOL_REF_P (x) && SYMBOL_REF_TLS_MODEL (x) != 0; |
| } |
| |
| /* Return true if symbol X binds locally. */ |
| |
| static bool |
| riscv_symbol_binds_local_p (const_rtx x) |
| { |
| if (SYMBOL_REF_P (x)) |
| return (SYMBOL_REF_DECL (x) |
| ? targetm.binds_local_p (SYMBOL_REF_DECL (x)) |
| : SYMBOL_REF_LOCAL_P (x)); |
| else |
| return false; |
| } |
| |
| /* Return the method that should be used to access SYMBOL_REF or |
| LABEL_REF X. */ |
| |
| static enum riscv_symbol_type |
| riscv_classify_symbol (const_rtx x) |
| { |
| if (riscv_tls_symbol_p (x)) |
| return SYMBOL_TLS; |
| |
| if (GET_CODE (x) == SYMBOL_REF && flag_pic && !riscv_symbol_binds_local_p (x)) |
| return SYMBOL_GOT_DISP; |
| |
| return riscv_cmodel == CM_MEDLOW ? SYMBOL_ABSOLUTE : SYMBOL_PCREL; |
| } |
| |
| /* Classify the base of symbolic expression X. */ |
| |
| enum riscv_symbol_type |
| riscv_classify_symbolic_expression (rtx x) |
| { |
| rtx offset; |
| |
| split_const (x, &x, &offset); |
| if (UNSPEC_ADDRESS_P (x)) |
| return UNSPEC_ADDRESS_TYPE (x); |
| |
| return riscv_classify_symbol (x); |
| } |
| |
| /* Return true if X is a symbolic constant. If it is, store the type of |
| the symbol in *SYMBOL_TYPE. */ |
| |
| bool |
| riscv_symbolic_constant_p (rtx x, enum riscv_symbol_type *symbol_type) |
| { |
| rtx offset; |
| |
| split_const (x, &x, &offset); |
| if (UNSPEC_ADDRESS_P (x)) |
| { |
| *symbol_type = UNSPEC_ADDRESS_TYPE (x); |
| x = UNSPEC_ADDRESS (x); |
| } |
| else if (GET_CODE (x) == SYMBOL_REF || GET_CODE (x) == LABEL_REF) |
| *symbol_type = riscv_classify_symbol (x); |
| else |
| return false; |
| |
| if (offset == const0_rtx) |
| return true; |
| |
| /* Nonzero offsets are only valid for references that don't use the GOT. */ |
| switch (*symbol_type) |
| { |
| case SYMBOL_ABSOLUTE: |
| case SYMBOL_PCREL: |
| case SYMBOL_TLS_LE: |
| /* GAS rejects offsets outside the range [-2^31, 2^31-1]. */ |
| return sext_hwi (INTVAL (offset), 32) == INTVAL (offset); |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* Returns the number of instructions necessary to reference a symbol. */ |
| |
| static int riscv_symbol_insns (enum riscv_symbol_type type) |
| { |
| switch (type) |
| { |
| case SYMBOL_TLS: return 0; /* Depends on the TLS model. */ |
| case SYMBOL_ABSOLUTE: return 2; /* LUI + the reference. */ |
| case SYMBOL_PCREL: return 2; /* AUIPC + the reference. */ |
| case SYMBOL_TLS_LE: return 3; /* LUI + ADD TP + the reference. */ |
| case SYMBOL_GOT_DISP: return 3; /* AUIPC + LD GOT + the reference. */ |
| default: gcc_unreachable (); |
| } |
| } |
| |
| /* Implement TARGET_LEGITIMATE_CONSTANT_P. */ |
| |
| static bool |
| riscv_legitimate_constant_p (machine_mode mode ATTRIBUTE_UNUSED, rtx x) |
| { |
| return riscv_const_insns (x) > 0; |
| } |
| |
| /* Implement TARGET_CANNOT_FORCE_CONST_MEM. */ |
| |
| static bool |
| riscv_cannot_force_const_mem (machine_mode mode ATTRIBUTE_UNUSED, rtx x) |
| { |
| enum riscv_symbol_type type; |
| rtx base, offset; |
| |
| /* There is no assembler syntax for expressing an address-sized |
| high part. */ |
| if (GET_CODE (x) == HIGH) |
| return true; |
| |
| split_const (x, &base, &offset); |
| if (riscv_symbolic_constant_p (base, &type)) |
| { |
| /* As an optimization, don't spill symbolic constants that are as |
| cheap to rematerialize as to access in the constant pool. */ |
| if (SMALL_OPERAND (INTVAL (offset)) && riscv_symbol_insns (type) > 0) |
| return true; |
| |
| /* As an optimization, avoid needlessly generate dynamic relocations. */ |
| if (flag_pic) |
| return true; |
| } |
| |
| /* TLS symbols must be computed by riscv_legitimize_move. */ |
| if (tls_referenced_p (x)) |
| return true; |
| |
| return false; |
| } |
| |
| /* Return true if register REGNO is a valid base register for mode MODE. |
| STRICT_P is true if REG_OK_STRICT is in effect. */ |
| |
| int |
| riscv_regno_mode_ok_for_base_p (int regno, |
| machine_mode mode ATTRIBUTE_UNUSED, |
| bool strict_p) |
| { |
| if (!HARD_REGISTER_NUM_P (regno)) |
| { |
| if (!strict_p) |
| return true; |
| regno = reg_renumber[regno]; |
| } |
| |
| /* These fake registers will be eliminated to either the stack or |
| hard frame pointer, both of which are usually valid base registers. |
| Reload deals with the cases where the eliminated form isn't valid. */ |
| if (regno == ARG_POINTER_REGNUM || regno == FRAME_POINTER_REGNUM) |
| return true; |
| |
| return GP_REG_P (regno); |
| } |
| |
| /* Return true if X is a valid base register for mode MODE. |
| STRICT_P is true if REG_OK_STRICT is in effect. */ |
| |
| static bool |
| riscv_valid_base_register_p (rtx x, machine_mode mode, bool strict_p) |
| { |
| if (!strict_p && GET_CODE (x) == SUBREG) |
| x = SUBREG_REG (x); |
| |
| return (REG_P (x) |
| && riscv_regno_mode_ok_for_base_p (REGNO (x), mode, strict_p)); |
| } |
| |
| /* Return true if, for every base register BASE_REG, (plus BASE_REG X) |
| can address a value of mode MODE. */ |
| |
| static bool |
| riscv_valid_offset_p (rtx x, machine_mode mode) |
| { |
| /* Check that X is a signed 12-bit number. */ |
| if (!const_arith_operand (x, Pmode)) |
| return false; |
| |
| /* We may need to split multiword moves, so make sure that every word |
| is accessible. */ |
| if (GET_MODE_SIZE (mode) > UNITS_PER_WORD |
| && !SMALL_OPERAND (INTVAL (x) + GET_MODE_SIZE (mode) - UNITS_PER_WORD)) |
| return false; |
| |
| return true; |
| } |
| |
| /* Should a symbol of type SYMBOL_TYPE should be split in two? */ |
| |
| bool |
| riscv_split_symbol_type (enum riscv_symbol_type symbol_type) |
| { |
| if (symbol_type == SYMBOL_TLS_LE) |
| return true; |
| |
| if (!TARGET_EXPLICIT_RELOCS) |
| return false; |
| |
| return symbol_type == SYMBOL_ABSOLUTE || symbol_type == SYMBOL_PCREL; |
| } |
| |
| /* Return true if a LO_SUM can address a value of mode MODE when the |
| LO_SUM symbol has type SYM_TYPE. X is the LO_SUM second operand, which |
| is used when the mode is BLKmode. */ |
| |
| static bool |
| riscv_valid_lo_sum_p (enum riscv_symbol_type sym_type, machine_mode mode, |
| rtx x) |
| { |
| int align, size; |
| |
| /* Check that symbols of type SYMBOL_TYPE can be used to access values |
| of mode MODE. */ |
| if (riscv_symbol_insns (sym_type) == 0) |
| return false; |
| |
| /* Check that there is a known low-part relocation. */ |
| if (!riscv_split_symbol_type (sym_type)) |
| return false; |
| |
| /* We can't tell size or alignment when we have BLKmode, so try extracing a |
| decl from the symbol if possible. */ |
| if (mode == BLKmode) |
| { |
| rtx offset; |
| |
| /* Extract the symbol from the LO_SUM operand, if any. */ |
| split_const (x, &x, &offset); |
| |
| /* Might be a CODE_LABEL. We can compute align but not size for that, |
| so don't bother trying to handle it. */ |
| if (!SYMBOL_REF_P (x)) |
| return false; |
| |
| /* Use worst case assumptions if we don't have a SYMBOL_REF_DECL. */ |
| align = (SYMBOL_REF_DECL (x) |
| ? DECL_ALIGN (SYMBOL_REF_DECL (x)) |
| : 1); |
| size = (SYMBOL_REF_DECL (x) && DECL_SIZE (SYMBOL_REF_DECL (x)) |
| ? tree_to_uhwi (DECL_SIZE (SYMBOL_REF_DECL (x))) |
| : 2*BITS_PER_WORD); |
| } |
| else |
| { |
| align = GET_MODE_ALIGNMENT (mode); |
| size = GET_MODE_BITSIZE (mode); |
| } |
| |
| /* We may need to split multiword moves, so make sure that each word |
| can be accessed without inducing a carry. */ |
| if (size > BITS_PER_WORD |
| && (!TARGET_STRICT_ALIGN || size > align)) |
| return false; |
| |
| return true; |
| } |
| |
| /* Return true if X is a valid address for machine mode MODE. If it is, |
| fill in INFO appropriately. STRICT_P is true if REG_OK_STRICT is in |
| effect. */ |
| |
| static bool |
| riscv_classify_address (struct riscv_address_info *info, rtx x, |
| machine_mode mode, bool strict_p) |
| { |
| switch (GET_CODE (x)) |
| { |
| case REG: |
| case SUBREG: |
| info->type = ADDRESS_REG; |
| info->reg = x; |
| info->offset = const0_rtx; |
| return riscv_valid_base_register_p (info->reg, mode, strict_p); |
| |
| case PLUS: |
| info->type = ADDRESS_REG; |
| info->reg = XEXP (x, 0); |
| info->offset = XEXP (x, 1); |
| return (riscv_valid_base_register_p (info->reg, mode, strict_p) |
| && riscv_valid_offset_p (info->offset, mode)); |
| |
| case LO_SUM: |
| info->type = ADDRESS_LO_SUM; |
| info->reg = XEXP (x, 0); |
| info->offset = XEXP (x, 1); |
| /* We have to trust the creator of the LO_SUM to do something vaguely |
| sane. Target-independent code that creates a LO_SUM should also |
| create and verify the matching HIGH. Target-independent code that |
| adds an offset to a LO_SUM must prove that the offset will not |
| induce a carry. Failure to do either of these things would be |
| a bug, and we are not required to check for it here. The RISC-V |
| backend itself should only create LO_SUMs for valid symbolic |
| constants, with the high part being either a HIGH or a copy |
| of _gp. */ |
| info->symbol_type |
| = riscv_classify_symbolic_expression (info->offset); |
| return (riscv_valid_base_register_p (info->reg, mode, strict_p) |
| && riscv_valid_lo_sum_p (info->symbol_type, mode, info->offset)); |
| |
| case CONST_INT: |
| /* Small-integer addresses don't occur very often, but they |
| are legitimate if x0 is a valid base register. */ |
| info->type = ADDRESS_CONST_INT; |
| return SMALL_OPERAND (INTVAL (x)); |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* Implement TARGET_LEGITIMATE_ADDRESS_P. */ |
| |
| static bool |
| riscv_legitimate_address_p (machine_mode mode, rtx x, bool strict_p) |
| { |
| struct riscv_address_info addr; |
| |
| return riscv_classify_address (&addr, x, mode, strict_p); |
| } |
| |
| /* Return true if hard reg REGNO can be used in compressed instructions. */ |
| |
| static bool |
| riscv_compressed_reg_p (int regno) |
| { |
| /* x8-x15/f8-f15 are compressible registers. */ |
| return (TARGET_RVC && (IN_RANGE (regno, GP_REG_FIRST + 8, GP_REG_FIRST + 15) |
| || IN_RANGE (regno, FP_REG_FIRST + 8, FP_REG_FIRST + 15))); |
| } |
| |
| /* Return true if x is an unsigned 5-bit immediate scaled by 4. */ |
| |
| static bool |
| riscv_compressed_lw_offset_p (rtx x) |
| { |
| return (CONST_INT_P (x) |
| && (INTVAL (x) & 3) == 0 |
| && IN_RANGE (INTVAL (x), 0, CSW_MAX_OFFSET)); |
| } |
| |
| /* Return true if load/store from/to address x can be compressed. */ |
| |
| static bool |
| riscv_compressed_lw_address_p (rtx x) |
| { |
| struct riscv_address_info addr; |
| bool result = riscv_classify_address (&addr, x, GET_MODE (x), |
| reload_completed); |
| |
| /* Return false if address is not compressed_reg + small_offset. */ |
| if (!result |
| || addr.type != ADDRESS_REG |
| /* Before reload, assume all registers are OK. */ |
| || (reload_completed |
| && !riscv_compressed_reg_p (REGNO (addr.reg)) |
| && addr.reg != stack_pointer_rtx) |
| || !riscv_compressed_lw_offset_p (addr.offset)) |
| return false; |
| |
| return result; |
| } |
| |
| /* Return the number of instructions needed to load or store a value |
| of mode MODE at address X. Return 0 if X isn't valid for MODE. |
| Assume that multiword moves may need to be split into word moves |
| if MIGHT_SPLIT_P, otherwise assume that a single load or store is |
| enough. */ |
| |
| int |
| riscv_address_insns (rtx x, machine_mode mode, bool might_split_p) |
| { |
| struct riscv_address_info addr = {}; |
| int n = 1; |
| |
| if (!riscv_classify_address (&addr, x, mode, false)) |
| { |
| /* This could be a pattern from the pic.md file. In which case we want |
| this address to always have a cost of 3 to make it as expensive as the |
| most expensive symbol. This prevents constant propagation from |
| preferring symbols over register plus offset. */ |
| return 3; |
| } |
| |
| /* BLKmode is used for single unaligned loads and stores and should |
| not count as a multiword mode. */ |
| if (mode != BLKmode && might_split_p) |
| n += (GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD; |
| |
| if (addr.type == ADDRESS_LO_SUM) |
| n += riscv_symbol_insns (addr.symbol_type) - 1; |
| |
| return n; |
| } |
| |
| /* Return the number of instructions needed to load constant X. |
| Return 0 if X isn't a valid constant. */ |
| |
| int |
| riscv_const_insns (rtx x) |
| { |
| enum riscv_symbol_type symbol_type; |
| rtx offset; |
| |
| switch (GET_CODE (x)) |
| { |
| case HIGH: |
| if (!riscv_symbolic_constant_p (XEXP (x, 0), &symbol_type) |
| || !riscv_split_symbol_type (symbol_type)) |
| return 0; |
| |
| /* This is simply an LUI. */ |
| return 1; |
| |
| case CONST_INT: |
| { |
| int cost = riscv_integer_cost (INTVAL (x)); |
| /* Force complicated constants to memory. */ |
| return cost < 4 ? cost : 0; |
| } |
| |
| case CONST_DOUBLE: |
| case CONST_VECTOR: |
| /* We can use x0 to load floating-point zero. */ |
| return x == CONST0_RTX (GET_MODE (x)) ? 1 : 0; |
| |
| case CONST: |
| /* See if we can refer to X directly. */ |
| if (riscv_symbolic_constant_p (x, &symbol_type)) |
| return riscv_symbol_insns (symbol_type); |
| |
| /* Otherwise try splitting the constant into a base and offset. */ |
| split_const (x, &x, &offset); |
| if (offset != 0) |
| { |
| int n = riscv_const_insns (x); |
| if (n != 0) |
| return n + riscv_integer_cost (INTVAL (offset)); |
| } |
| return 0; |
| |
| case SYMBOL_REF: |
| case LABEL_REF: |
| return riscv_symbol_insns (riscv_classify_symbol (x)); |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* X is a doubleword constant that can be handled by splitting it into |
| two words and loading each word separately. Return the number of |
| instructions required to do this. */ |
| |
| int |
| riscv_split_const_insns (rtx x) |
| { |
| unsigned int low, high; |
| |
| low = riscv_const_insns (riscv_subword (x, false)); |
| high = riscv_const_insns (riscv_subword (x, true)); |
| gcc_assert (low > 0 && high > 0); |
| return low + high; |
| } |
| |
| /* Return the number of instructions needed to implement INSN, |
| given that it loads from or stores to MEM. */ |
| |
| int |
| riscv_load_store_insns (rtx mem, rtx_insn *insn) |
| { |
| machine_mode mode; |
| bool might_split_p; |
| rtx set; |
| |
| gcc_assert (MEM_P (mem)); |
| mode = GET_MODE (mem); |
| |
| /* Try to prove that INSN does not need to be split. */ |
| might_split_p = true; |
| if (GET_MODE_BITSIZE (mode) <= 32) |
| might_split_p = false; |
| else if (GET_MODE_BITSIZE (mode) == 64) |
| { |
| set = single_set (insn); |
| if (set && !riscv_split_64bit_move_p (SET_DEST (set), SET_SRC (set))) |
| might_split_p = false; |
| } |
| |
| return riscv_address_insns (XEXP (mem, 0), mode, might_split_p); |
| } |
| |
| /* Emit a move from SRC to DEST. Assume that the move expanders can |
| handle all moves if !can_create_pseudo_p (). The distinction is |
| important because, unlike emit_move_insn, the move expanders know |
| how to force Pmode objects into the constant pool even when the |
| constant pool address is not itself legitimate. */ |
| |
| rtx |
| riscv_emit_move (rtx dest, rtx src) |
| { |
| return (can_create_pseudo_p () |
| ? emit_move_insn (dest, src) |
| : emit_move_insn_1 (dest, src)); |
| } |
| |
| /* Emit an instruction of the form (set TARGET SRC). */ |
| |
| static rtx |
| riscv_emit_set (rtx target, rtx src) |
| { |
| emit_insn (gen_rtx_SET (target, src)); |
| return target; |
| } |
| |
| /* Emit an instruction of the form (set DEST (CODE X Y)). */ |
| |
| static rtx |
| riscv_emit_binary (enum rtx_code code, rtx dest, rtx x, rtx y) |
| { |
| return riscv_emit_set (dest, gen_rtx_fmt_ee (code, GET_MODE (dest), x, y)); |
| } |
| |
| /* Compute (CODE X Y) and store the result in a new register |
| of mode MODE. Return that new register. */ |
| |
| static rtx |
| riscv_force_binary (machine_mode mode, enum rtx_code code, rtx x, rtx y) |
| { |
| return riscv_emit_binary (code, gen_reg_rtx (mode), x, y); |
| } |
| |
| static rtx |
| riscv_swap_instruction (rtx inst) |
| { |
| gcc_assert (GET_MODE (inst) == SImode); |
| if (BYTES_BIG_ENDIAN) |
| inst = expand_unop (SImode, bswap_optab, inst, gen_reg_rtx (SImode), 1); |
| return inst; |
| } |
| |
| /* Copy VALUE to a register and return that register. If new pseudos |
| are allowed, copy it into a new register, otherwise use DEST. */ |
| |
| static rtx |
| riscv_force_temporary (rtx dest, rtx value, bool in_splitter) |
| { |
| /* We can't call gen_reg_rtx from a splitter, because this might realloc |
| the regno_reg_rtx array, which would invalidate reg rtx pointers in the |
| combine undo buffer. */ |
| if (can_create_pseudo_p () && !in_splitter) |
| return force_reg (Pmode, value); |
| else |
| { |
| riscv_emit_move (dest, value); |
| return dest; |
| } |
| } |
| |
| /* Wrap symbol or label BASE in an UNSPEC address of type SYMBOL_TYPE, |
| then add CONST_INT OFFSET to the result. */ |
| |
| static rtx |
| riscv_unspec_address_offset (rtx base, rtx offset, |
| enum riscv_symbol_type symbol_type) |
| { |
| base = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, base), |
| UNSPEC_ADDRESS_FIRST + symbol_type); |
| if (offset != const0_rtx) |
| base = gen_rtx_PLUS (Pmode, base, offset); |
| return gen_rtx_CONST (Pmode, base); |
| } |
| |
| /* Return an UNSPEC address with underlying address ADDRESS and symbol |
| type SYMBOL_TYPE. */ |
| |
| rtx |
| riscv_unspec_address (rtx address, enum riscv_symbol_type symbol_type) |
| { |
| rtx base, offset; |
| |
| split_const (address, &base, &offset); |
| return riscv_unspec_address_offset (base, offset, symbol_type); |
| } |
| |
| /* If OP is an UNSPEC address, return the address to which it refers, |
| otherwise return OP itself. */ |
| |
| static rtx |
| riscv_strip_unspec_address (rtx op) |
| { |
| rtx base, offset; |
| |
| split_const (op, &base, &offset); |
| if (UNSPEC_ADDRESS_P (base)) |
| op = plus_constant (Pmode, UNSPEC_ADDRESS (base), INTVAL (offset)); |
| return op; |
| } |
| |
| /* If riscv_unspec_address (ADDR, SYMBOL_TYPE) is a 32-bit value, add the |
| high part to BASE and return the result. Just return BASE otherwise. |
| TEMP is as for riscv_force_temporary. |
| |
| The returned expression can be used as the first operand to a LO_SUM. */ |
| |
| static rtx |
| riscv_unspec_offset_high (rtx temp, rtx addr, enum riscv_symbol_type symbol_type) |
| { |
| addr = gen_rtx_HIGH (Pmode, riscv_unspec_address (addr, symbol_type)); |
| return riscv_force_temporary (temp, addr, FALSE); |
| } |
| |
| /* Load an entry from the GOT for a TLS GD access. */ |
| |
| static rtx riscv_got_load_tls_gd (rtx dest, rtx sym) |
| { |
| if (Pmode == DImode) |
| return gen_got_load_tls_gddi (dest, sym); |
| else |
| return gen_got_load_tls_gdsi (dest, sym); |
| } |
| |
| /* Load an entry from the GOT for a TLS IE access. */ |
| |
| static rtx riscv_got_load_tls_ie (rtx dest, rtx sym) |
| { |
| if (Pmode == DImode) |
| return gen_got_load_tls_iedi (dest, sym); |
| else |
| return gen_got_load_tls_iesi (dest, sym); |
| } |
| |
| /* Add in the thread pointer for a TLS LE access. */ |
| |
| static rtx riscv_tls_add_tp_le (rtx dest, rtx base, rtx sym) |
| { |
| rtx tp = gen_rtx_REG (Pmode, THREAD_POINTER_REGNUM); |
| if (Pmode == DImode) |
| return gen_tls_add_tp_ledi (dest, base, tp, sym); |
| else |
| return gen_tls_add_tp_lesi (dest, base, tp, sym); |
| } |
| |
| /* If MODE is MAX_MACHINE_MODE, ADDR appears as a move operand, otherwise |
| it appears in a MEM of that mode. Return true if ADDR is a legitimate |
| constant in that context and can be split into high and low parts. |
| If so, and if LOW_OUT is nonnull, emit the high part and store the |
| low part in *LOW_OUT. Leave *LOW_OUT unchanged otherwise. |
| |
| TEMP is as for riscv_force_temporary and is used to load the high |
| part into a register. |
| |
| When MODE is MAX_MACHINE_MODE, the low part is guaranteed to be |
| a legitimize SET_SRC for an .md pattern, otherwise the low part |
| is guaranteed to be a legitimate address for mode MODE. */ |
| |
| bool |
| riscv_split_symbol (rtx temp, rtx addr, machine_mode mode, rtx *low_out, |
| bool in_splitter) |
| { |
| enum riscv_symbol_type symbol_type; |
| |
| if ((GET_CODE (addr) == HIGH && mode == MAX_MACHINE_MODE) |
| || !riscv_symbolic_constant_p (addr, &symbol_type) |
| || riscv_symbol_insns (symbol_type) == 0 |
| || !riscv_split_symbol_type (symbol_type)) |
| return false; |
| |
| if (low_out) |
| switch (symbol_type) |
| { |
| case SYMBOL_ABSOLUTE: |
| { |
| rtx high = gen_rtx_HIGH (Pmode, copy_rtx (addr)); |
| high = riscv_force_temporary (temp, high, in_splitter); |
| *low_out = gen_rtx_LO_SUM (Pmode, high, addr); |
| } |
| break; |
| |
| case SYMBOL_PCREL: |
| { |
| static unsigned seqno; |
| char buf[32]; |
| rtx label; |
| |
| ssize_t bytes = snprintf (buf, sizeof (buf), ".LA%u", seqno); |
| gcc_assert ((size_t) bytes < sizeof (buf)); |
| |
| label = gen_rtx_SYMBOL_REF (Pmode, ggc_strdup (buf)); |
| SYMBOL_REF_FLAGS (label) |= SYMBOL_FLAG_LOCAL; |
| /* ??? Ugly hack to make weak symbols work. May need to change the |
| RTL for the auipc and/or low patterns to get a better fix for |
| this. */ |
| if (! nonzero_address_p (addr)) |
| SYMBOL_REF_WEAK (label) = 1; |
| |
| if (temp == NULL) |
| temp = gen_reg_rtx (Pmode); |
| |
| if (Pmode == DImode) |
| emit_insn (gen_auipcdi (temp, copy_rtx (addr), GEN_INT (seqno))); |
| else |
| emit_insn (gen_auipcsi (temp, copy_rtx (addr), GEN_INT (seqno))); |
| |
| *low_out = gen_rtx_LO_SUM (Pmode, temp, label); |
| |
| seqno++; |
| } |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| return true; |
| } |
| |
| /* Return a legitimate address for REG + OFFSET. TEMP is as for |
| riscv_force_temporary; it is only needed when OFFSET is not a |
| SMALL_OPERAND. */ |
| |
| static rtx |
| riscv_add_offset (rtx temp, rtx reg, HOST_WIDE_INT offset) |
| { |
| if (!SMALL_OPERAND (offset)) |
| { |
| rtx high; |
| |
| /* Leave OFFSET as a 16-bit offset and put the excess in HIGH. |
| The addition inside the macro CONST_HIGH_PART may cause an |
| overflow, so we need to force a sign-extension check. */ |
| high = gen_int_mode (CONST_HIGH_PART (offset), Pmode); |
| offset = CONST_LOW_PART (offset); |
| high = riscv_force_temporary (temp, high, FALSE); |
| reg = riscv_force_temporary (temp, gen_rtx_PLUS (Pmode, high, reg), |
| FALSE); |
| } |
| return plus_constant (Pmode, reg, offset); |
| } |
| |
| /* The __tls_get_attr symbol. */ |
| static GTY(()) rtx riscv_tls_symbol; |
| |
| /* Return an instruction sequence that calls __tls_get_addr. SYM is |
| the TLS symbol we are referencing and TYPE is the symbol type to use |
| (either global dynamic or local dynamic). RESULT is an RTX for the |
| return value location. */ |
| |
| static rtx_insn * |
| riscv_call_tls_get_addr (rtx sym, rtx result) |
| { |
| rtx a0 = gen_rtx_REG (Pmode, GP_ARG_FIRST), func; |
| rtx_insn *insn; |
| |
| if (!riscv_tls_symbol) |
| riscv_tls_symbol = init_one_libfunc ("__tls_get_addr"); |
| func = gen_rtx_MEM (FUNCTION_MODE, riscv_tls_symbol); |
| |
| start_sequence (); |
| |
| emit_insn (riscv_got_load_tls_gd (a0, sym)); |
| insn = emit_call_insn (gen_call_value (result, func, const0_rtx, NULL)); |
| RTL_CONST_CALL_P (insn) = 1; |
| use_reg (&CALL_INSN_FUNCTION_USAGE (insn), a0); |
| insn = get_insns (); |
| |
| end_sequence (); |
| |
| return insn; |
| } |
| |
| /* Generate the code to access LOC, a thread-local SYMBOL_REF, and return |
| its address. The return value will be both a valid address and a valid |
| SET_SRC (either a REG or a LO_SUM). */ |
| |
| static rtx |
| riscv_legitimize_tls_address (rtx loc) |
| { |
| rtx dest, tp, tmp; |
| enum tls_model model = SYMBOL_REF_TLS_MODEL (loc); |
| |
| #if 0 |
| /* TLS copy relocs are now deprecated and should not be used. */ |
| /* Since we support TLS copy relocs, non-PIC TLS accesses may all use LE. */ |
| if (!flag_pic) |
| model = TLS_MODEL_LOCAL_EXEC; |
| #endif |
| |
| switch (model) |
| { |
| case TLS_MODEL_LOCAL_DYNAMIC: |
| /* Rely on section anchors for the optimization that LDM TLS |
| provides. The anchor's address is loaded with GD TLS. */ |
| case TLS_MODEL_GLOBAL_DYNAMIC: |
| tmp = gen_rtx_REG (Pmode, GP_RETURN); |
| dest = gen_reg_rtx (Pmode); |
| emit_libcall_block (riscv_call_tls_get_addr (loc, tmp), dest, tmp, loc); |
| break; |
| |
| case TLS_MODEL_INITIAL_EXEC: |
| /* la.tls.ie; tp-relative add */ |
| tp = gen_rtx_REG (Pmode, THREAD_POINTER_REGNUM); |
| tmp = gen_reg_rtx (Pmode); |
| emit_insn (riscv_got_load_tls_ie (tmp, loc)); |
| dest = gen_reg_rtx (Pmode); |
| emit_insn (gen_add3_insn (dest, tmp, tp)); |
| break; |
| |
| case TLS_MODEL_LOCAL_EXEC: |
| tmp = riscv_unspec_offset_high (NULL, loc, SYMBOL_TLS_LE); |
| dest = gen_reg_rtx (Pmode); |
| emit_insn (riscv_tls_add_tp_le (dest, tmp, loc)); |
| dest = gen_rtx_LO_SUM (Pmode, dest, |
| riscv_unspec_address (loc, SYMBOL_TLS_LE)); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| return dest; |
| } |
| |
| /* If X is not a valid address for mode MODE, force it into a register. */ |
| |
| static rtx |
| riscv_force_address (rtx x, machine_mode mode) |
| { |
| if (!riscv_legitimate_address_p (mode, x, false)) |
| x = force_reg (Pmode, x); |
| return x; |
| } |
| |
| /* Modify base + offset so that offset fits within a compressed load/store insn |
| and the excess is added to base. */ |
| |
| static rtx |
| riscv_shorten_lw_offset (rtx base, HOST_WIDE_INT offset) |
| { |
| rtx addr, high; |
| /* Leave OFFSET as an unsigned 5-bit offset scaled by 4 and put the excess |
| into HIGH. */ |
| high = GEN_INT (offset & ~CSW_MAX_OFFSET); |
| offset &= CSW_MAX_OFFSET; |
| if (!SMALL_OPERAND (INTVAL (high))) |
| high = force_reg (Pmode, high); |
| base = force_reg (Pmode, gen_rtx_PLUS (Pmode, high, base)); |
| addr = plus_constant (Pmode, base, offset); |
| return addr; |
| } |
| |
| /* This function is used to implement LEGITIMIZE_ADDRESS. If X can |
| be legitimized in a way that the generic machinery might not expect, |
| return a new address, otherwise return NULL. MODE is the mode of |
| the memory being accessed. */ |
| |
| static rtx |
| riscv_legitimize_address (rtx x, rtx oldx ATTRIBUTE_UNUSED, |
| machine_mode mode) |
| { |
| rtx addr; |
| |
| if (riscv_tls_symbol_p (x)) |
| return riscv_legitimize_tls_address (x); |
| |
| /* See if the address can split into a high part and a LO_SUM. */ |
| if (riscv_split_symbol (NULL, x, mode, &addr, FALSE)) |
| return riscv_force_address (addr, mode); |
| |
| /* Handle BASE + OFFSET. */ |
| if (GET_CODE (x) == PLUS && CONST_INT_P (XEXP (x, 1)) |
| && INTVAL (XEXP (x, 1)) != 0) |
| { |
| rtx base = XEXP (x, 0); |
| HOST_WIDE_INT offset = INTVAL (XEXP (x, 1)); |
| |
| if (!riscv_valid_base_register_p (base, mode, false)) |
| base = copy_to_mode_reg (Pmode, base); |
| if (optimize_function_for_size_p (cfun) |
| && (strcmp (current_pass->name, "shorten_memrefs") == 0) |
| && mode == SImode) |
| /* Convert BASE + LARGE_OFFSET into NEW_BASE + SMALL_OFFSET to allow |
| possible compressed load/store. */ |
| addr = riscv_shorten_lw_offset (base, offset); |
| else |
| addr = riscv_add_offset (NULL, base, offset); |
| return riscv_force_address (addr, mode); |
| } |
| |
| return x; |
| } |
| |
| /* Load VALUE into DEST. TEMP is as for riscv_force_temporary. ORIG_MODE |
| is the original src mode before promotion. */ |
| |
| void |
| riscv_move_integer (rtx temp, rtx dest, HOST_WIDE_INT value, |
| machine_mode orig_mode, bool in_splitter) |
| { |
| struct riscv_integer_op codes[RISCV_MAX_INTEGER_OPS]; |
| machine_mode mode; |
| int i, num_ops; |
| rtx x; |
| |
| /* We can't call gen_reg_rtx from a splitter, because this might realloc |
| the regno_reg_rtx array, which would invalidate reg rtx pointers in the |
| combine undo buffer. */ |
| bool can_create_pseudo = can_create_pseudo_p () && ! in_splitter; |
| |
| mode = GET_MODE (dest); |
| /* We use the original mode for the riscv_build_integer call, because HImode |
| values are given special treatment. */ |
| num_ops = riscv_build_integer (codes, value, orig_mode); |
| |
| if (can_create_pseudo && num_ops > 2 /* not a simple constant */ |
| && num_ops >= riscv_split_integer_cost (value)) |
| x = riscv_split_integer (value, mode); |
| else |
| { |
| /* Apply each binary operation to X. */ |
| x = GEN_INT (codes[0].value); |
| |
| for (i = 1; i < num_ops; i++) |
| { |
| if (!can_create_pseudo) |
| x = riscv_emit_set (temp, x); |
| else |
| x = force_reg (mode, x); |
| |
| x = gen_rtx_fmt_ee (codes[i].code, mode, x, GEN_INT (codes[i].value)); |
| } |
| } |
| |
| riscv_emit_set (dest, x); |
| } |
| |
| /* Subroutine of riscv_legitimize_move. Move constant SRC into register |
| DEST given that SRC satisfies immediate_operand but doesn't satisfy |
| move_operand. */ |
| |
| static void |
| riscv_legitimize_const_move (machine_mode mode, rtx dest, rtx src) |
| { |
| rtx base, offset; |
| |
| /* Split moves of big integers into smaller pieces. */ |
| if (splittable_const_int_operand (src, mode)) |
| { |
| riscv_move_integer (dest, dest, INTVAL (src), mode, FALSE); |
| return; |
| } |
| |
| /* Split moves of symbolic constants into high/low pairs. */ |
| if (riscv_split_symbol (dest, src, MAX_MACHINE_MODE, &src, FALSE)) |
| { |
| riscv_emit_set (dest, src); |
| return; |
| } |
| |
| /* Generate the appropriate access sequences for TLS symbols. */ |
| if (riscv_tls_symbol_p (src)) |
| { |
| riscv_emit_move (dest, riscv_legitimize_tls_address (src)); |
| return; |
| } |
| |
| /* If we have (const (plus symbol offset)), and that expression cannot |
| be forced into memory, load the symbol first and add in the offset. Also |
| prefer to do this even if the constant _can_ be forced into memory, as it |
| usually produces better code. */ |
| split_const (src, &base, &offset); |
| if (offset != const0_rtx |
| && (targetm.cannot_force_const_mem (mode, src) || can_create_pseudo_p ())) |
| { |
| base = riscv_force_temporary (dest, base, FALSE); |
| riscv_emit_move (dest, riscv_add_offset (NULL, base, INTVAL (offset))); |
| return; |
| } |
| |
| src = force_const_mem (mode, src); |
| |
| /* When using explicit relocs, constant pool references are sometimes |
| not legitimate addresses. */ |
| riscv_split_symbol (dest, XEXP (src, 0), mode, &XEXP (src, 0), FALSE); |
| riscv_emit_move (dest, src); |
| } |
| |
| /* If (set DEST SRC) is not a valid move instruction, emit an equivalent |
| sequence that is valid. */ |
| |
| bool |
| riscv_legitimize_move (machine_mode mode, rtx dest, rtx src) |
| { |
| /* Expand |
| (set (reg:QI target) (mem:QI (address))) |
| to |
| (set (reg:DI temp) (zero_extend:DI (mem:QI (address)))) |
| (set (reg:QI target) (subreg:QI (reg:DI temp) 0)) |
| with auto-sign/zero extend. */ |
| if (GET_MODE_CLASS (mode) == MODE_INT |
| && GET_MODE_SIZE (mode) < UNITS_PER_WORD |
| && can_create_pseudo_p () |
| && MEM_P (src)) |
| { |
| rtx temp_reg; |
| int zero_extend_p; |
| |
| temp_reg = gen_reg_rtx (word_mode); |
| zero_extend_p = (LOAD_EXTEND_OP (mode) == ZERO_EXTEND); |
| emit_insn (gen_extend_insn (temp_reg, src, word_mode, mode, |
| zero_extend_p)); |
| riscv_emit_move (dest, gen_lowpart (mode, temp_reg)); |
| return true; |
| } |
| |
| if (!register_operand (dest, mode) && !reg_or_0_operand (src, mode)) |
| { |
| rtx reg; |
| |
| if (GET_CODE (src) == CONST_INT) |
| { |
| /* Apply the equivalent of PROMOTE_MODE here for constants to |
| improve cse. */ |
| machine_mode promoted_mode = mode; |
| if (GET_MODE_CLASS (mode) == MODE_INT |
| && GET_MODE_SIZE (mode) < UNITS_PER_WORD) |
| promoted_mode = word_mode; |
| |
| if (splittable_const_int_operand (src, mode)) |
| { |
| reg = gen_reg_rtx (promoted_mode); |
| riscv_move_integer (reg, reg, INTVAL (src), mode, FALSE); |
| } |
| else |
| reg = force_reg (promoted_mode, src); |
| |
| if (promoted_mode != mode) |
| reg = gen_lowpart (mode, reg); |
| } |
| else |
| reg = force_reg (mode, src); |
| riscv_emit_move (dest, reg); |
| return true; |
| } |
| |
| /* We need to deal with constants that would be legitimate |
| immediate_operands but aren't legitimate move_operands. */ |
| if (CONSTANT_P (src) && !move_operand (src, mode)) |
| { |
| riscv_legitimize_const_move (mode, dest, src); |
| set_unique_reg_note (get_last_insn (), REG_EQUAL, copy_rtx (src)); |
| return true; |
| } |
| |
| /* RISC-V GCC may generate non-legitimate address due to we provide some |
| pattern for optimize access PIC local symbol and it's make GCC generate |
| unrecognizable instruction during optmizing. */ |
| |
| if (MEM_P (dest) && !riscv_legitimate_address_p (mode, XEXP (dest, 0), |
| reload_completed)) |
| { |
| XEXP (dest, 0) = riscv_force_address (XEXP (dest, 0), mode); |
| } |
| |
| if (MEM_P (src) && !riscv_legitimate_address_p (mode, XEXP (src, 0), |
| reload_completed)) |
| { |
| XEXP (src, 0) = riscv_force_address (XEXP (src, 0), mode); |
| } |
| |
| return false; |
| } |
| |
| /* Return true if there is an instruction that implements CODE and accepts |
| X as an immediate operand. */ |
| |
| static int |
| riscv_immediate_operand_p (int code, HOST_WIDE_INT x) |
| { |
| switch (code) |
| { |
| case ASHIFT: |
| case ASHIFTRT: |
| case LSHIFTRT: |
| /* All shift counts are truncated to a valid constant. */ |
| return true; |
| |
| case AND: |
| case IOR: |
| case XOR: |
| case PLUS: |
| case LT: |
| case LTU: |
| /* These instructions take 12-bit signed immediates. */ |
| return SMALL_OPERAND (x); |
| |
| case LE: |
| /* We add 1 to the immediate and use SLT. */ |
| return SMALL_OPERAND (x + 1); |
| |
| case LEU: |
| /* Likewise SLTU, but reject the always-true case. */ |
| return SMALL_OPERAND (x + 1) && x + 1 != 0; |
| |
| case GE: |
| case GEU: |
| /* We can emulate an immediate of 1 by using GT/GTU against x0. */ |
| return x == 1; |
| |
| default: |
| /* By default assume that x0 can be used for 0. */ |
| return x == 0; |
| } |
| } |
| |
| /* Return the cost of binary operation X, given that the instruction |
| sequence for a word-sized or smaller operation takes SIGNLE_INSNS |
| instructions and that the sequence of a double-word operation takes |
| DOUBLE_INSNS instructions. */ |
| |
| static int |
| riscv_binary_cost (rtx x, int single_insns, int double_insns) |
| { |
| if (GET_MODE_SIZE (GET_MODE (x)) == UNITS_PER_WORD * 2) |
| return COSTS_N_INSNS (double_insns); |
| return COSTS_N_INSNS (single_insns); |
| } |
| |
| /* Return the cost of sign- or zero-extending OP. */ |
| |
| static int |
| riscv_extend_cost (rtx op, bool unsigned_p) |
| { |
| if (MEM_P (op)) |
| return 0; |
| |
| if (unsigned_p && GET_MODE (op) == QImode) |
| /* We can use ANDI. */ |
| return COSTS_N_INSNS (1); |
| |
| if (!unsigned_p && GET_MODE (op) == SImode) |
| /* We can use SEXT.W. */ |
| return COSTS_N_INSNS (1); |
| |
| /* We need to use a shift left and a shift right. */ |
| return COSTS_N_INSNS (2); |
| } |
| |
| /* Implement TARGET_RTX_COSTS. */ |
| |
| #define SINGLE_SHIFT_COST 1 |
| |
| static bool |
| riscv_rtx_costs (rtx x, machine_mode mode, int outer_code, int opno ATTRIBUTE_UNUSED, |
| int *total, bool speed) |
| { |
| bool float_mode_p = FLOAT_MODE_P (mode); |
| int cost; |
| |
| switch (GET_CODE (x)) |
| { |
| case CONST_INT: |
| if (riscv_immediate_operand_p (outer_code, INTVAL (x))) |
| { |
| *total = 0; |
| return true; |
| } |
| /* Fall through. */ |
| |
| case SYMBOL_REF: |
| case LABEL_REF: |
| case CONST_DOUBLE: |
| case CONST: |
| if ((cost = riscv_const_insns (x)) > 0) |
| { |
| /* If the constant is likely to be stored in a GPR, SETs of |
| single-insn constants are as cheap as register sets; we |
| never want to CSE them. */ |
| if (cost == 1 && outer_code == SET) |
| *total = 0; |
| /* When we load a constant more than once, it usually is better |
| to duplicate the last operation in the sequence than to CSE |
| the constant itself. */ |
| else if (outer_code == SET || GET_MODE (x) == VOIDmode) |
| *total = COSTS_N_INSNS (1); |
| } |
| else /* The instruction will be fetched from the constant pool. */ |
| *total = COSTS_N_INSNS (riscv_symbol_insns (SYMBOL_ABSOLUTE)); |
| return true; |
| |
| case MEM: |
| /* If the address is legitimate, return the number of |
| instructions it needs. */ |
| if ((cost = riscv_address_insns (XEXP (x, 0), mode, true)) > 0) |
| { |
| /* When optimizing for size, make uncompressible 32-bit addresses |
| more expensive so that compressible 32-bit addresses are |
| preferred. */ |
| if (TARGET_RVC && !speed && riscv_mshorten_memrefs && mode == SImode |
| && !riscv_compressed_lw_address_p (XEXP (x, 0))) |
| cost++; |
| |
| *total = COSTS_N_INSNS (cost + tune_param->memory_cost); |
| return true; |
| } |
| /* Otherwise use the default handling. */ |
| return false; |
| |
| case NOT: |
| *total = COSTS_N_INSNS (GET_MODE_SIZE (mode) > UNITS_PER_WORD ? 2 : 1); |
| return false; |
| |
| case AND: |
| case IOR: |
| case XOR: |
| /* Double-word operations use two single-word operations. */ |
| *total = riscv_binary_cost (x, 1, 2); |
| return false; |
| |
| case ZERO_EXTRACT: |
| /* This is an SImode shift. */ |
| if (outer_code == SET |
| && CONST_INT_P (XEXP (x, 1)) |
| && CONST_INT_P (XEXP (x, 2)) |
| && (INTVAL (XEXP (x, 2)) > 0) |
| && (INTVAL (XEXP (x, 1)) + INTVAL (XEXP (x, 2)) == 32)) |
| { |
| *total = COSTS_N_INSNS (SINGLE_SHIFT_COST); |
| return true; |
| } |
| return false; |
| |
| case ASHIFT: |
| case ASHIFTRT: |
| case LSHIFTRT: |
| *total = riscv_binary_cost (x, SINGLE_SHIFT_COST, |
| CONSTANT_P (XEXP (x, 1)) ? 4 : 9); |
| return false; |
| |
| case ABS: |
| *total = COSTS_N_INSNS (float_mode_p ? 1 : 3); |
| return false; |
| |
| case LO_SUM: |
| *total = set_src_cost (XEXP (x, 0), mode, speed); |
| return true; |
| |
| case LT: |
| /* This is an SImode shift. */ |
| if (outer_code == SET && GET_MODE (x) == DImode |
| && GET_MODE (XEXP (x, 0)) == SImode) |
| { |
| *total = COSTS_N_INSNS (SINGLE_SHIFT_COST); |
| return true; |
| } |
| /* Fall through. */ |
| case LTU: |
| case LE: |
| case LEU: |
| case GT: |
| case GTU: |
| case GE: |
| case GEU: |
| case EQ: |
| case NE: |
| /* Branch comparisons have VOIDmode, so use the first operand's |
| mode instead. */ |
| mode = GET_MODE (XEXP (x, 0)); |
| if (float_mode_p) |
| *total = tune_param->fp_add[mode == DFmode]; |
| else |
| *total = riscv_binary_cost (x, 1, 3); |
| return false; |
| |
| case UNORDERED: |
| case ORDERED: |
| /* (FEQ(A, A) & FEQ(B, B)) compared against 0. */ |
| mode = GET_MODE (XEXP (x, 0)); |
| *total = tune_param->fp_add[mode == DFmode] + COSTS_N_INSNS (2); |
| return false; |
| |
| case UNEQ: |
| /* (FEQ(A, A) & FEQ(B, B)) compared against FEQ(A, B). */ |
| mode = GET_MODE (XEXP (x, 0)); |
| *total = tune_param->fp_add[mode == DFmode] + COSTS_N_INSNS (3); |
| return false; |
| |
| case LTGT: |
| /* (FLT(A, A) || FGT(B, B)). */ |
| mode = GET_MODE (XEXP (x, 0)); |
| *total = tune_param->fp_add[mode == DFmode] + COSTS_N_INSNS (2); |
| return false; |
| |
| case UNGE: |
| case UNGT: |
| case UNLE: |
| case UNLT: |
| /* FLT or FLE, but guarded by an FFLAGS read and write. */ |
| mode = GET_MODE (XEXP (x, 0)); |
| *total = tune_param->fp_add[mode == DFmode] + COSTS_N_INSNS (4); |
| return false; |
| |
| case MINUS: |
| case PLUS: |
| if (float_mode_p) |
| *total = tune_param->fp_add[mode == DFmode]; |
| else |
| *total = riscv_binary_cost (x, 1, 4); |
| return false; |
| |
| case NEG: |
| { |
| rtx op = XEXP (x, 0); |
| if (GET_CODE (op) == FMA && !HONOR_SIGNED_ZEROS (mode)) |
| { |
| *total = (tune_param->fp_mul[mode == DFmode] |
| + set_src_cost (XEXP (op, 0), mode, speed) |
| + set_src_cost (XEXP (op, 1), mode, speed) |
| + set_src_cost (XEXP (op, 2), mode, speed)); |
| return true; |
| } |
| } |
| |
| if (float_mode_p) |
| *total = tune_param->fp_add[mode == DFmode]; |
| else |
| *total = COSTS_N_INSNS (GET_MODE_SIZE (mode) > UNITS_PER_WORD ? 4 : 1); |
| return false; |
| |
| case MULT: |
| if (float_mode_p) |
| *total = tune_param->fp_mul[mode == DFmode]; |
| else if (!TARGET_MUL) |
| /* Estimate the cost of a library call. */ |
| *total = COSTS_N_INSNS (speed ? 32 : 6); |
| else if (GET_MODE_SIZE (mode) > UNITS_PER_WORD) |
| *total = 3 * tune_param->int_mul[0] + COSTS_N_INSNS (2); |
| else if (!speed) |
| *total = COSTS_N_INSNS (1); |
| else |
| *total = tune_param->int_mul[mode == DImode]; |
| return false; |
| |
| case DIV: |
| case SQRT: |
| case MOD: |
| if (float_mode_p) |
| { |
| *total = tune_param->fp_div[mode == DFmode]; |
| return false; |
| } |
| /* Fall through. */ |
| |
| case UDIV: |
| case UMOD: |
| if (!TARGET_DIV) |
| /* Estimate the cost of a library call. */ |
| *total = COSTS_N_INSNS (speed ? 32 : 6); |
| else if (speed) |
| *total = tune_param->int_div[mode == DImode]; |
| else |
| *total = COSTS_N_INSNS (1); |
| return false; |
| |
| case ZERO_EXTEND: |
| /* This is an SImode shift. */ |
| if (GET_CODE (XEXP (x, 0)) == LSHIFTRT) |
| { |
| *total = COSTS_N_INSNS (SINGLE_SHIFT_COST); |
| return true; |
| } |
| /* Fall through. */ |
| case SIGN_EXTEND: |
| *total = riscv_extend_cost (XEXP (x, 0), GET_CODE (x) == ZERO_EXTEND); |
| return false; |
| |
| case FLOAT: |
| case UNSIGNED_FLOAT: |
| case FIX: |
| case FLOAT_EXTEND: |
| case FLOAT_TRUNCATE: |
| *total = tune_param->fp_add[mode == DFmode]; |
| return false; |
| |
| case FMA: |
| *total = (tune_param->fp_mul[mode == DFmode] |
| + set_src_cost (XEXP (x, 0), mode, speed) |
| + set_src_cost (XEXP (x, 1), mode, speed) |
| + set_src_cost (XEXP (x, 2), mode, speed)); |
| return true; |
| |
| case UNSPEC: |
| if (XINT (x, 1) == UNSPEC_AUIPC) |
| { |
| /* Make AUIPC cheap to avoid spilling its result to the stack. */ |
| *total = 1; |
| return true; |
| } |
| return false; |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* Implement TARGET_ADDRESS_COST. */ |
| |
| static int |
| riscv_address_cost (rtx addr, machine_mode mode, |
| addr_space_t as ATTRIBUTE_UNUSED, |
| bool speed ATTRIBUTE_UNUSED) |
| { |
| /* When optimizing for size, make uncompressible 32-bit addresses more |
| * expensive so that compressible 32-bit addresses are preferred. */ |
| if (TARGET_RVC && !speed && riscv_mshorten_memrefs && mode == SImode |
| && !riscv_compressed_lw_address_p (addr)) |
| return riscv_address_insns (addr, mode, false) + 1; |
| return riscv_address_insns (addr, mode, false); |
| } |
| |
| /* Return one word of double-word value OP. HIGH_P is true to select the |
| high part or false to select the low part. */ |
| |
| rtx |
| riscv_subword (rtx op, bool high_p) |
| { |
| unsigned int byte = (high_p != BYTES_BIG_ENDIAN) ? UNITS_PER_WORD : 0; |
| machine_mode mode = GET_MODE (op); |
| |
| if (mode == VOIDmode) |
| mode = TARGET_64BIT ? TImode : DImode; |
| |
| if (MEM_P (op)) |
| return adjust_address (op, word_mode, byte); |
| |
| if (REG_P (op)) |
| gcc_assert (!FP_REG_RTX_P (op)); |
| |
| return simplify_gen_subreg (word_mode, op, mode, byte); |
| } |
| |
| /* Return true if a 64-bit move from SRC to DEST should be split into two. */ |
| |
| bool |
| riscv_split_64bit_move_p (rtx dest, rtx src) |
| { |
| if (TARGET_64BIT) |
| return false; |
| |
| /* Allow FPR <-> FPR and FPR <-> MEM moves, and permit the special case |
| of zeroing an FPR with FCVT.D.W. */ |
| if (TARGET_DOUBLE_FLOAT |
| && ((FP_REG_RTX_P (src) && FP_REG_RTX_P (dest)) |
| || (FP_REG_RTX_P (dest) && MEM_P (src)) |
| || (FP_REG_RTX_P (src) && MEM_P (dest)) |
| || (FP_REG_RTX_P (dest) && src == CONST0_RTX (GET_MODE (src))))) |
| return false; |
| |
| return true; |
| } |
| |
| /* Split a doubleword move from SRC to DEST. On 32-bit targets, |
| this function handles 64-bit moves for which riscv_split_64bit_move_p |
| holds. For 64-bit targets, this function handles 128-bit moves. */ |
| |
| void |
| riscv_split_doubleword_move (rtx dest, rtx src) |
| { |
| rtx low_dest; |
| |
| /* The operation can be split into two normal moves. Decide in |
| which order to do them. */ |
| low_dest = riscv_subword (dest, false); |
| if (REG_P (low_dest) && reg_overlap_mentioned_p (low_dest, src)) |
| { |
| riscv_emit_move (riscv_subword (dest, true), riscv_subword (src, true)); |
| riscv_emit_move (low_dest, riscv_subword (src, false)); |
| } |
| else |
| { |
| riscv_emit_move (low_dest, riscv_subword (src, false)); |
| riscv_emit_move (riscv_subword (dest, true), riscv_subword (src, true)); |
| } |
| } |
| |
| /* Return the appropriate instructions to move SRC into DEST. Assume |
| that SRC is operand 1 and DEST is operand 0. */ |
| |
| const char * |
| riscv_output_move (rtx dest, rtx src) |
| { |
| enum rtx_code dest_code, src_code; |
| machine_mode mode; |
| bool dbl_p; |
| |
| dest_code = GET_CODE (dest); |
| src_code = GET_CODE (src); |
| mode = GET_MODE (dest); |
| dbl_p = (GET_MODE_SIZE (mode) == 8); |
| |
| if (dbl_p && riscv_split_64bit_move_p (dest, src)) |
| return "#"; |
| |
| if (dest_code == REG && GP_REG_P (REGNO (dest))) |
| { |
| if (src_code == REG && FP_REG_P (REGNO (src))) |
| return dbl_p ? "fmv.x.d\t%0,%1" : "fmv.x.w\t%0,%1"; |
| |
| if (src_code == MEM) |
| switch (GET_MODE_SIZE (mode)) |
| { |
| case 1: return "lbu\t%0,%1"; |
| case 2: return "lhu\t%0,%1"; |
| case 4: return "lw\t%0,%1"; |
| case 8: return "ld\t%0,%1"; |
| } |
| |
| if (src_code == CONST_INT) |
| return "li\t%0,%1"; |
| |
| if (src_code == HIGH) |
| return "lui\t%0,%h1"; |
| |
| if (symbolic_operand (src, VOIDmode)) |
| switch (riscv_classify_symbolic_expression (src)) |
| { |
| case SYMBOL_GOT_DISP: return "la\t%0,%1"; |
| case SYMBOL_ABSOLUTE: return "lla\t%0,%1"; |
| case SYMBOL_PCREL: return "lla\t%0,%1"; |
| default: gcc_unreachable (); |
| } |
| } |
| if ((src_code == REG && GP_REG_P (REGNO (src))) |
| || (src == CONST0_RTX (mode))) |
| { |
| if (dest_code == REG) |
| { |
| if (GP_REG_P (REGNO (dest))) |
| return "mv\t%0,%z1"; |
| |
| if (FP_REG_P (REGNO (dest))) |
| { |
| if (!dbl_p) |
| return "fmv.w.x\t%0,%z1"; |
| if (TARGET_64BIT) |
| return "fmv.d.x\t%0,%z1"; |
| /* in RV32, we can emulate fmv.d.x %0, x0 using fcvt.d.w */ |
| gcc_assert (src == CONST0_RTX (mode)); |
| return "fcvt.d.w\t%0,x0"; |
| } |
| } |
| if (dest_code == MEM) |
| switch (GET_MODE_SIZE (mode)) |
| { |
| case 1: return "sb\t%z1,%0"; |
| case 2: return "sh\t%z1,%0"; |
| case 4: return "sw\t%z1,%0"; |
| case 8: return "sd\t%z1,%0"; |
| } |
| } |
| if (src_code == REG && FP_REG_P (REGNO (src))) |
| { |
| if (dest_code == REG && FP_REG_P (REGNO (dest))) |
| return dbl_p ? "fmv.d\t%0,%1" : "fmv.s\t%0,%1"; |
| |
| if (dest_code == MEM) |
| return dbl_p ? "fsd\t%1,%0" : "fsw\t%1,%0"; |
| } |
| if (dest_code == REG && FP_REG_P (REGNO (dest))) |
| { |
| if (src_code == MEM) |
| return dbl_p ? "fld\t%0,%1" : "flw\t%0,%1"; |
| } |
| gcc_unreachable (); |
| } |
| |
| const char * |
| riscv_output_return () |
| { |
| if (cfun->machine->naked_p) |
| return ""; |
| |
| return "ret"; |
| } |
| |
| |
| /* Return true if CMP1 is a suitable second operand for integer ordering |
| test CODE. See also the *sCC patterns in riscv.md. */ |
| |
| static bool |
| riscv_int_order_operand_ok_p (enum rtx_code code, rtx cmp1) |
| { |
| switch (code) |
| { |
| case GT: |
| case GTU: |
| return reg_or_0_operand (cmp1, VOIDmode); |
| |
| case GE: |
| case GEU: |
| return cmp1 == const1_rtx; |
| |
| case LT: |
| case LTU: |
| return arith_operand (cmp1, VOIDmode); |
| |
| case LE: |
| return sle_operand (cmp1, VOIDmode); |
| |
| case LEU: |
| return sleu_operand (cmp1, VOIDmode); |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Return true if *CMP1 (of mode MODE) is a valid second operand for |
| integer ordering test *CODE, or if an equivalent combination can |
| be formed by adjusting *CODE and *CMP1. When returning true, update |
| *CODE and *CMP1 with the chosen code and operand, otherwise leave |
| them alone. */ |
| |
| static bool |
| riscv_canonicalize_int_order_test (enum rtx_code *code, rtx *cmp1, |
| machine_mode mode) |
| { |
| HOST_WIDE_INT plus_one; |
| |
| if (riscv_int_order_operand_ok_p (*code, *cmp1)) |
| return true; |
| |
| if (CONST_INT_P (*cmp1)) |
| switch (*code) |
| { |
| case LE: |
| plus_one = trunc_int_for_mode (UINTVAL (*cmp1) + 1, mode); |
| if (INTVAL (*cmp1) < plus_one) |
| { |
| *code = LT; |
| *cmp1 = force_reg (mode, GEN_INT (plus_one)); |
| return true; |
| } |
| break; |
| |
| case LEU: |
| plus_one = trunc_int_for_mode (UINTVAL (*cmp1) + 1, mode); |
| if (plus_one != 0) |
| { |
| *code = LTU; |
| *cmp1 = force_reg (mode, GEN_INT (plus_one)); |
| return true; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| return false; |
| } |
| |
| /* Compare CMP0 and CMP1 using ordering test CODE and store the result |
| in TARGET. CMP0 and TARGET are register_operands. If INVERT_PTR |
| is nonnull, it's OK to set TARGET to the inverse of the result and |
| flip *INVERT_PTR instead. */ |
| |
| static void |
| riscv_emit_int_order_test (enum rtx_code code, bool *invert_ptr, |
| rtx target, rtx cmp0, rtx cmp1) |
| { |
| machine_mode mode; |
| |
| /* First see if there is a RISCV instruction that can do this operation. |
| If not, try doing the same for the inverse operation. If that also |
| fails, force CMP1 into a register and try again. */ |
| mode = GET_MODE (cmp0); |
| if (riscv_canonicalize_int_order_test (&code, &cmp1, mode)) |
| riscv_emit_binary (code, target, cmp0, cmp1); |
| else |
| { |
| enum rtx_code inv_code = reverse_condition (code); |
| if (!riscv_canonicalize_int_order_test (&inv_code, &cmp1, mode)) |
| { |
| cmp1 = force_reg (mode, cmp1); |
| riscv_emit_int_order_test (code, invert_ptr, target, cmp0, cmp1); |
| } |
| else if (invert_ptr == 0) |
| { |
| rtx inv_target = riscv_force_binary (GET_MODE (target), |
| inv_code, cmp0, cmp1); |
| riscv_emit_binary (XOR, target, inv_target, const1_rtx); |
| } |
| else |
| { |
| *invert_ptr = !*invert_ptr; |
| riscv_emit_binary (inv_code, target, cmp0, cmp1); |
| } |
| } |
| } |
| |
| /* Return a register that is zero iff CMP0 and CMP1 are equal. |
| The register will have the same mode as CMP0. */ |
| |
| static rtx |
| riscv_zero_if_equal (rtx cmp0, rtx cmp1) |
| { |
| if (cmp1 == const0_rtx) |
| return cmp0; |
| |
| return expand_binop (GET_MODE (cmp0), sub_optab, |
| cmp0, cmp1, 0, 0, OPTAB_DIRECT); |
| } |
| |
| /* Sign- or zero-extend OP0 and OP1 for integer comparisons. */ |
| |
| static void |
| riscv_extend_comparands (rtx_code code, rtx *op0, rtx *op1) |
| { |
| /* Comparisons consider all XLEN bits, so extend sub-XLEN values. */ |
| if (GET_MODE_SIZE (word_mode) > GET_MODE_SIZE (GET_MODE (*op0))) |
| { |
| /* It is more profitable to zero-extend QImode values. But not if the |
| first operand has already been sign-extended, and the second one is |
| is a constant or has already been sign-extended also. */ |
| if (unsigned_condition (code) == code |
| && (GET_MODE (*op0) == QImode |
| && ! (GET_CODE (*op0) == SUBREG |
| && SUBREG_PROMOTED_VAR_P (*op0) |
| && SUBREG_PROMOTED_SIGNED_P (*op0) |
| && (CONST_INT_P (*op1) |
| || (GET_CODE (*op1) == SUBREG |
| && SUBREG_PROMOTED_VAR_P (*op1) |
| && SUBREG_PROMOTED_SIGNED_P (*op1)))))) |
| { |
| *op0 = gen_rtx_ZERO_EXTEND (word_mode, *op0); |
| if (CONST_INT_P (*op1)) |
| *op1 = GEN_INT ((uint8_t) INTVAL (*op1)); |
| else |
| *op1 = gen_rtx_ZERO_EXTEND (word_mode, *op1); |
| } |
| else |
| { |
| *op0 = gen_rtx_SIGN_EXTEND (word_mode, *op0); |
| if (*op1 != const0_rtx) |
| *op1 = gen_rtx_SIGN_EXTEND (word_mode, *op1); |
| } |
| } |
| } |
| |
| /* Convert a comparison into something that can be used in a branch. On |
| entry, *OP0 and *OP1 are the values being compared and *CODE is the code |
| used to compare them. Update them to describe the final comparison. */ |
| |
| static void |
| riscv_emit_int_compare (enum rtx_code *code, rtx *op0, rtx *op1) |
| { |
| if (splittable_const_int_operand (*op1, VOIDmode)) |
| { |
| HOST_WIDE_INT rhs = INTVAL (*op1); |
| |
| if (*code == EQ || *code == NE) |
| { |
| /* Convert e.g. OP0 == 2048 into OP0 - 2048 == 0. */ |
| if (SMALL_OPERAND (-rhs)) |
| { |
| *op0 = riscv_force_binary (GET_MODE (*op0), PLUS, *op0, |
| GEN_INT (-rhs)); |
| *op1 = const0_rtx; |
| } |
| } |
| else |
| { |
| static const enum rtx_code mag_comparisons[][2] = { |
| {LEU, LTU}, {GTU, GEU}, {LE, LT}, {GT, GE} |
| }; |
| |
| /* Convert e.g. (OP0 <= 0xFFF) into (OP0 < 0x1000). */ |
| for (size_t i = 0; i < ARRAY_SIZE (mag_comparisons); i++) |
| { |
| HOST_WIDE_INT new_rhs; |
| bool increment = *code == mag_comparisons[i][0]; |
| bool decrement = *code == mag_comparisons[i][1]; |
| if (!increment && !decrement) |
| continue; |
| |
| new_rhs = rhs + (increment ? 1 : -1); |
| if (riscv_integer_cost (new_rhs) < riscv_integer_cost (rhs) |
| && (rhs < 0) == (new_rhs < 0)) |
| { |
| *op1 = GEN_INT (new_rhs); |
| *code = mag_comparisons[i][increment]; |
| } |
| break; |
| } |
| } |
| } |
| |
| riscv_extend_comparands (*code, op0, op1); |
| |
| *op0 = force_reg (word_mode, *op0); |
| if (*op1 != const0_rtx) |
| *op1 = force_reg (word_mode, *op1); |
| } |
| |
| /* Like riscv_emit_int_compare, but for floating-point comparisons. */ |
| |
| static void |
| riscv_emit_float_compare (enum rtx_code *code, rtx *op0, rtx *op1) |
| { |
| rtx tmp0, tmp1, cmp_op0 = *op0, cmp_op1 = *op1; |
| enum rtx_code fp_code = *code; |
| *code = NE; |
| |
| switch (fp_code) |
| { |
| case UNORDERED: |
| *code = EQ; |
| /* Fall through. */ |
| |
| case ORDERED: |
| /* a == a && b == b */ |
| tmp0 = riscv_force_binary (word_mode, EQ, cmp_op0, cmp_op0); |
| tmp1 = riscv_force_binary (word_mode, EQ, cmp_op1, cmp_op1); |
| *op0 = riscv_force_binary (word_mode, AND, tmp0, tmp1); |
| *op1 = const0_rtx; |
| break; |
| |
| case UNEQ: |
| /* ordered(a, b) > (a == b) */ |
| *code = EQ; |
| tmp0 = riscv_force_binary (word_mode, EQ, cmp_op0, cmp_op0); |
| tmp1 = riscv_force_binary (word_mode, EQ, cmp_op1, cmp_op1); |
| *op0 = riscv_force_binary (word_mode, AND, tmp0, tmp1); |
| *op1 = riscv_force_binary (word_mode, EQ, cmp_op0, cmp_op1); |
| break; |
| |
| #define UNORDERED_COMPARISON(CODE, CMP) \ |
| case CODE: \ |
| *code = EQ; \ |
| *op0 = gen_reg_rtx (word_mode); \ |
| if (GET_MODE (cmp_op0) == SFmode && TARGET_64BIT) \ |
| emit_insn (gen_f##CMP##_quietsfdi4 (*op0, cmp_op0, cmp_op1)); \ |
| else if (GET_MODE (cmp_op0) == SFmode) \ |
| emit_insn (gen_f##CMP##_quietsfsi4 (*op0, cmp_op0, cmp_op1)); \ |
| else if (GET_MODE (cmp_op0) == DFmode && TARGET_64BIT) \ |
| emit_insn (gen_f##CMP##_quietdfdi4 (*op0, cmp_op0, cmp_op1)); \ |
| else if (GET_MODE (cmp_op0) == DFmode) \ |
| emit_insn (gen_f##CMP##_quietdfsi4 (*op0, cmp_op0, cmp_op1)); \ |
| else \ |
| gcc_unreachable (); \ |
| *op1 = const0_rtx; \ |
| break; |
| |
| case UNLT: |
| std::swap (cmp_op0, cmp_op1); |
| gcc_fallthrough (); |
| |
| UNORDERED_COMPARISON(UNGT, le) |
| |
| case UNLE: |
| std::swap (cmp_op0, cmp_op1); |
| gcc_fallthrough (); |
| |
| UNORDERED_COMPARISON(UNGE, lt) |
| #undef UNORDERED_COMPARISON |
| |
| case NE: |
| fp_code = EQ; |
| *code = EQ; |
| /* Fall through. */ |
| |
| case EQ: |
| case LE: |
| case LT: |
| case GE: |
| case GT: |
| /* We have instructions for these cases. */ |
| *op0 = riscv_force_binary (word_mode, fp_code, cmp_op0, cmp_op1); |
| *op1 = const0_rtx; |
| break; |
| |
| case LTGT: |
| /* (a < b) | (a > b) */ |
| tmp0 = riscv_force_binary (word_mode, LT, cmp_op0, cmp_op1); |
| tmp1 = riscv_force_binary (word_mode, GT, cmp_op0, cmp_op1); |
| *op0 = riscv_force_binary (word_mode, IOR, tmp0, tmp1); |
| *op1 = const0_rtx; |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* CODE-compare OP0 and OP1. Store the result in TARGET. */ |
| |
| void |
| riscv_expand_int_scc (rtx target, enum rtx_code code, rtx op0, rtx op1) |
| { |
| riscv_extend_comparands (code, &op0, &op1); |
| op0 = force_reg (word_mode, op0); |
| |
| if (code == EQ || code == NE) |
| { |
| rtx zie = riscv_zero_if_equal (op0, op1); |
| riscv_emit_binary (code, target, zie, const0_rtx); |
| } |
| else |
| riscv_emit_int_order_test (code, 0, target, op0, op1); |
| } |
| |
| /* Like riscv_expand_int_scc, but for floating-point comparisons. */ |
| |
| void |
| riscv_expand_float_scc (rtx target, enum rtx_code code, rtx op0, rtx op1) |
| { |
| riscv_emit_float_compare (&code, &op0, &op1); |
| |
| rtx cmp = riscv_force_binary (word_mode, code, op0, op1); |
| riscv_emit_set (target, lowpart_subreg (SImode, cmp, word_mode)); |
| } |
| |
| /* Jump to LABEL if (CODE OP0 OP1) holds. */ |
| |
| void |
| riscv_expand_conditional_branch (rtx label, rtx_code code, rtx op0, rtx op1) |
| { |
| if (FLOAT_MODE_P (GET_MODE (op1))) |
| riscv_emit_float_compare (&code, &op0, &op1); |
| else |
| riscv_emit_int_compare (&code, &op0, &op1); |
| |
| rtx condition = gen_rtx_fmt_ee (code, VOIDmode, op0, op1); |
| emit_jump_insn (gen_condjump (condition, label)); |
| } |
| |
| /* If (CODE OP0 OP1) holds, move CONS to DEST; else move ALT to DEST. */ |
| |
| void |
| riscv_expand_conditional_move (rtx dest, rtx cons, rtx alt, rtx_code code, |
| rtx op0, rtx op1) |
| { |
| riscv_emit_int_compare (&code, &op0, &op1); |
| rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1); |
| emit_insn (gen_rtx_SET (dest, gen_rtx_IF_THEN_ELSE (GET_MODE (dest), cond, |
| cons, alt))); |
| } |
| |
| /* Implement TARGET_FUNCTION_ARG_BOUNDARY. Every parameter gets at |
| least PARM_BOUNDARY bits of alignment, but will be given anything up |
| to PREFERRED_STACK_BOUNDARY bits if the type requires it. */ |
| |
| static unsigned int |
| riscv_function_arg_boundary (machine_mode mode, const_tree type) |
| { |
| unsigned int alignment; |
| |
| /* Use natural alignment if the type is not aggregate data. */ |
| if (type && !AGGREGATE_TYPE_P (type)) |
| alignment = TYPE_ALIGN (TYPE_MAIN_VARIANT (type)); |
| else |
| alignment = type ? TYPE_ALIGN (type) : GET_MODE_ALIGNMENT (mode); |
| |
| return MIN (PREFERRED_STACK_BOUNDARY, MAX (PARM_BOUNDARY, alignment)); |
| } |
| |
| /* If MODE represents an argument that can be passed or returned in |
| floating-point registers, return the number of registers, else 0. */ |
| |
| static unsigned |
| riscv_pass_mode_in_fpr_p (machine_mode mode) |
| { |
| if (GET_MODE_UNIT_SIZE (mode) <= UNITS_PER_FP_ARG) |
| { |
| if (GET_MODE_CLASS (mode) == MODE_FLOAT) |
| return 1; |
| |
| if (GET_MODE_CLASS (mode) == MODE_COMPLEX_FLOAT) |
| return 2; |
| } |
| |
| return 0; |
| } |
| |
| typedef struct { |
| const_tree type; |
| HOST_WIDE_INT offset; |
| } riscv_aggregate_field; |
| |
| /* Identify subfields of aggregates that are candidates for passing in |
| floating-point registers. */ |
| |
| static int |
| riscv_flatten_aggregate_field (const_tree type, |
| riscv_aggregate_field fields[2], |
| int n, HOST_WIDE_INT offset, |
| bool ignore_zero_width_bit_field_p) |
| { |
| switch (TREE_CODE (type)) |
| { |
| case RECORD_TYPE: |
| /* Can't handle incomplete types nor sizes that are not fixed. */ |
| if (!COMPLETE_TYPE_P (type) |
| || TREE_CODE (TYPE_SIZE (type)) != INTEGER_CST |
| || !tree_fits_uhwi_p (TYPE_SIZE (type))) |
| return -1; |
| |
| for (tree f = TYPE_FIELDS (type); f; f = DECL_CHAIN (f)) |
| if (TREE_CODE (f) == FIELD_DECL) |
| { |
| if (!TYPE_P (TREE_TYPE (f))) |
| return -1; |
| |
| /* The C++ front end strips zero-length bit-fields from structs. |
| So we need to ignore them in the C front end to make C code |
| compatible with C++ code. */ |
| if (ignore_zero_width_bit_field_p |
| && DECL_BIT_FIELD (f) |
| && (DECL_SIZE (f) == NULL_TREE |
| || integer_zerop (DECL_SIZE (f)))) |
| ; |
| else |
| { |
| HOST_WIDE_INT pos = offset + int_byte_position (f); |
| n = riscv_flatten_aggregate_field (TREE_TYPE (f), |
| fields, n, pos, |
| ignore_zero_width_bit_field_p); |
| } |
| if (n < 0) |
| return -1; |
| } |
| return n; |
| |
| case ARRAY_TYPE: |
| { |
| HOST_WIDE_INT n_elts; |
| riscv_aggregate_field subfields[2]; |
| tree index = TYPE_DOMAIN (type); |
| tree elt_size = TYPE_SIZE_UNIT (TREE_TYPE (type)); |
| int n_subfields = riscv_flatten_aggregate_field (TREE_TYPE (type), |
| subfields, 0, offset, |
| ignore_zero_width_bit_field_p); |
| |
| /* Can't handle incomplete types nor sizes that are not fixed. */ |
| if (n_subfields <= 0 |
| || !COMPLETE_TYPE_P (type) |
| || TREE_CODE (TYPE_SIZE (type)) != INTEGER_CST |
| || !index |
| || !TYPE_MAX_VALUE (index) |
| || !tree_fits_uhwi_p (TYPE_MAX_VALUE (index)) |
| || !TYPE_MIN_VALUE (index) |
| || !tree_fits_uhwi_p (TYPE_MIN_VALUE (index)) |
| || !tree_fits_uhwi_p (elt_size)) |
| return -1; |
| |
| n_elts = 1 + tree_to_uhwi (TYPE_MAX_VALUE (index)) |
| - tree_to_uhwi (TYPE_MIN_VALUE (index)); |
| gcc_assert (n_elts >= 0); |
| |
| for (HOST_WIDE_INT i = 0; i < n_elts; i++) |
| for (int j = 0; j < n_subfields; j++) |
| { |
| if (n >= 2) |
| return -1; |
| |
| fields[n] = subfields[j]; |
| fields[n++].offset += i * tree_to_uhwi (elt_size); |
| } |
| |
| return n; |
| } |
| |
| case COMPLEX_TYPE: |
| { |
| /* Complex type need consume 2 field, so n must be 0. */ |
| if (n != 0) |
| return -1; |
| |
| HOST_WIDE_INT elt_size = GET_MODE_SIZE (TYPE_MODE (TREE_TYPE (type))); |
| |
| if (elt_size <= UNITS_PER_FP_ARG) |
| { |
| fields[0].type = TREE_TYPE (type); |
| fields[0].offset = offset; |
| fields[1].type = TREE_TYPE (type); |
| fields[1].offset = offset + elt_size; |
| |
| return 2; |
| } |
| |
| return -1; |
| } |
| |
| default: |
| if (n < 2 |
| && ((SCALAR_FLOAT_TYPE_P (type) |
| && GET_MODE_SIZE (TYPE_MODE (type)) <= UNITS_PER_FP_ARG) |
| || (INTEGRAL_TYPE_P (type) |
| && GET_MODE_SIZE (TYPE_MODE (type)) <= UNITS_PER_WORD))) |
| { |
| fields[n].type = type; |
| fields[n].offset = offset; |
| return n + 1; |
| } |
| else |
| return -1; |
| } |
| } |
| |
| /* Identify candidate aggregates for passing in floating-point registers. |
| Candidates have at most two fields after flattening. */ |
| |
| static int |
| riscv_flatten_aggregate_argument (const_tree type, |
| riscv_aggregate_field fields[2], |
| bool ignore_zero_width_bit_field_p) |
| { |
| if (!type || TREE_CODE (type) != RECORD_TYPE) |
| return -1; |
| |
| return riscv_flatten_aggregate_field (type, fields, 0, 0, |
| ignore_zero_width_bit_field_p); |
| } |
| |
| /* See whether TYPE is a record whose fields should be returned in one or |
| two floating-point registers. If so, populate FIELDS accordingly. */ |
| |
| static unsigned |
| riscv_pass_aggregate_in_fpr_pair_p (const_tree type, |
| riscv_aggregate_field fields[2]) |
| { |
| static int warned = 0; |
| |
| /* This is the old ABI, which differs for C++ and C. */ |
| int n_old = riscv_flatten_aggregate_argument (type, fields, false); |
| for (int i = 0; i < n_old; i++) |
| if (!SCALAR_FLOAT_TYPE_P (fields[i].type)) |
| { |
| n_old = -1; |
| break; |
| } |
| |
| /* This is the new ABI, which is the same for C++ and C. */ |
| int n_new = riscv_flatten_aggregate_argument (type, fields, true); |
| for (int i = 0; i < n_new; i++) |
| if (!SCALAR_FLOAT_TYPE_P (fields[i].type)) |
| { |
| n_new = -1; |
| break; |
| } |
| |
| if ((n_old != n_new) && (warned == 0)) |
| { |
| warning (0, "ABI for flattened struct with zero-length bit-fields " |
| "changed in GCC 10"); |
| warned = 1; |
| } |
| |
| return n_new > 0 ? n_new : 0; |
| } |
| |
| /* See whether TYPE is a record whose fields should be returned in one or |
| floating-point register and one integer register. If so, populate |
| FIELDS accordingly. */ |
| |
| static bool |
| riscv_pass_aggregate_in_fpr_and_gpr_p (const_tree type, |
| riscv_aggregate_field fields[2]) |
| { |
| static int warned = 0; |
| |
| /* This is the old ABI, which differs for C++ and C. */ |
| unsigned num_int_old = 0, num_float_old = 0; |
| int n_old = riscv_flatten_aggregate_argument (type, fields, false); |
| for (int i = 0; i < n_old; i++) |
| { |
| num_float_old += SCALAR_FLOAT_TYPE_P (fields[i].type); |
| num_int_old += INTEGRAL_TYPE_P (fields[i].type); |
| } |
| |
| /* This is the new ABI, which is the same for C++ and C. */ |
| unsigned num_int_new = 0, num_float_new = 0; |
| int n_new = riscv_flatten_aggregate_argument (type, fields, true); |
| for (int i = 0; i < n_new; i++) |
| { |
| num_float_new += SCALAR_FLOAT_TYPE_P (fields[i].type); |
| num_int_new += INTEGRAL_TYPE_P (fields[i].type); |
| } |
| |
| if (((num_int_old == 1 && num_float_old == 1 |
| && (num_int_old != num_int_new || num_float_old != num_float_new)) |
| || (num_int_new == 1 && num_float_new == 1 |
| && (num_int_old != num_int_new || num_float_old != num_float_new))) |
| && (warned == 0)) |
| { |
| warning (0, "ABI for flattened struct with zero-length bit-fields " |
| "changed in GCC 10"); |
| warned = 1; |
| } |
| |
| return num_int_new == 1 && num_float_new == 1; |
| } |
| |
| /* Return the representation of an argument passed or returned in an FPR |
| when the value has mode VALUE_MODE and the type has TYPE_MODE. The |
| two modes may be different for structures like: |
| |
| struct __attribute__((packed)) foo { float f; } |
| |
| where the SFmode value "f" is passed in REGNO but the struct itself |
| has mode BLKmode. */ |
| |
| static rtx |
| riscv_pass_fpr_single (machine_mode type_mode, unsigned regno, |
| machine_mode value_mode, |
| HOST_WIDE_INT offset) |
| { |
| rtx x = gen_rtx_REG (value_mode, regno); |
| |
| if (type_mode != value_mode) |
| { |
| x = gen_rtx_EXPR_LIST (VOIDmode, x, GEN_INT (offset)); |
| x = gen_rtx_PARALLEL (type_mode, gen_rtvec (1, x)); |
| } |
| return x; |
| } |
| |
| /* Pass or return a composite value in the FPR pair REGNO and REGNO + 1. |
| MODE is the mode of the composite. MODE1 and OFFSET1 are the mode and |
| byte offset for the first value, likewise MODE2 and OFFSET2 for the |
| second value. */ |
| |
| static rtx |
| riscv_pass_fpr_pair (machine_mode mode, unsigned regno1, |
| machine_mode mode1, HOST_WIDE_INT offset1, |
| unsigned regno2, machine_mode mode2, |
| HOST_WIDE_INT offset2) |
| { |
| return gen_rtx_PARALLEL |
| (mode, |
| gen_rtvec (2, |
| gen_rtx_EXPR_LIST (VOIDmode, |
| gen_rtx_REG (mode1, regno1), |
| GEN_INT (offset1)), |
| gen_rtx_EXPR_LIST (VOIDmode, |
| gen_rtx_REG (mode2, regno2), |
| GEN_INT (offset2)))); |
| } |
| |
| /* Fill INFO with information about a single argument, and return an |
| RTL pattern to pass or return the argument. CUM is the cumulative |
| state for earlier arguments. MODE is the mode of this argument and |
| TYPE is its type (if known). NAMED is true if this is a named |
| (fixed) argument rather than a variable one. RETURN_P is true if |
| returning the argument, or false if passing the argument. */ |
| |
| static rtx |
| riscv_get_arg_info (struct riscv_arg_info *info, const CUMULATIVE_ARGS *cum, |
| machine_mode mode, const_tree type, bool named, |
| bool return_p) |
| { |
| unsigned num_bytes, num_words; |
| unsigned fpr_base = return_p ? FP_RETURN : FP_ARG_FIRST; |
| unsigned gpr_base = return_p ? GP_RETURN : GP_ARG_FIRST; |
| unsigned alignment = riscv_function_arg_boundary (mode, type); |
| |
| memset (info, 0, sizeof (*info)); |
| info->gpr_offset = cum->num_gprs; |
| info->fpr_offset = cum->num_fprs; |
| |
| if (named) |
| { |
| riscv_aggregate_field fields[2]; |
| unsigned fregno = fpr_base + info->fpr_offset; |
| unsigned gregno = gpr_base + info->gpr_offset; |
| |
| /* Pass one- or two-element floating-point aggregates in FPRs. */ |
| if ((info->num_fprs = riscv_pass_aggregate_in_fpr_pair_p (type, fields)) |
| && info->fpr_offset + info->num_fprs <= MAX_ARGS_IN_REGISTERS) |
| switch (info->num_fprs) |
| { |
| case 1: |
| return riscv_pass_fpr_single (mode, fregno, |
| TYPE_MODE (fields[0].type), |
| fields[0].offset); |
| |
| case 2: |
| return riscv_pass_fpr_pair (mode, fregno, |
| TYPE_MODE (fields[0].type), |
| fields[0].offset, |
| fregno + 1, |
| TYPE_MODE (fields[1].type), |
| fields[1].offset); |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| /* Pass real and complex floating-point numbers in FPRs. */ |
| if ((info->num_fprs = riscv_pass_mode_in_fpr_p (mode)) |
| && info->fpr_offset + info->num_fprs <= MAX_ARGS_IN_REGISTERS) |
| switch (GET_MODE_CLASS (mode)) |
| { |
| case MODE_FLOAT: |
| return gen_rtx_REG (mode, fregno); |
| |
| case MODE_COMPLEX_FLOAT: |
| return riscv_pass_fpr_pair (mode, fregno, GET_MODE_INNER (mode), 0, |
| fregno + 1, GET_MODE_INNER (mode), |
| GET_MODE_UNIT_SIZE (mode)); |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| /* Pass structs with one float and one integer in an FPR and a GPR. */ |
| if (riscv_pass_aggregate_in_fpr_and_gpr_p (type, fields) |
| && info->gpr_offset < MAX_ARGS_IN_REGISTERS |
| && info->fpr_offset < MAX_ARGS_IN_REGISTERS) |
| { |
| info->num_gprs = 1; |
| info->num_fprs = 1; |
| |
| if (!SCALAR_FLOAT_TYPE_P (fields[0].type)) |
| std::swap (fregno, gregno); |
| |
| return riscv_pass_fpr_pair (mode, fregno, TYPE_MODE (fields[0].type), |
| fields[0].offset, |
| gregno, TYPE_MODE (fields[1].type), |
| fields[1].offset); |
| } |
| } |
| |
| /* Work out the size of the argument. */ |
| num_bytes = type ? int_size_in_bytes (type) : GET_MODE_SIZE (mode); |
| num_words = (num_bytes + UNITS_PER_WORD - 1) / UNITS_PER_WORD; |
| |
| /* Doubleword-aligned varargs start on an even register boundary. */ |
| if (!named && num_bytes != 0 && alignment > BITS_PER_WORD) |
| info->gpr_offset += info->gpr_offset & 1; |
| |
| /* Partition the argument between registers and stack. */ |
| info->num_fprs = 0; |
| info->num_gprs = MIN (num_words, MAX_ARGS_IN_REGISTERS - info->gpr_offset); |
| info->stack_p = (num_words - info->num_gprs) != 0; |
| |
| if (info->num_gprs || return_p) |
| return gen_rtx_REG (mode, gpr_base + info->gpr_offset); |
| |
| return NULL_RTX; |
| } |
| |
| /* Implement TARGET_FUNCTION_ARG. */ |
| |
| static rtx |
| riscv_function_arg (cumulative_args_t cum_v, const function_arg_info &arg) |
| { |
| CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); |
| struct riscv_arg_info info; |
| |
| if (arg.end_marker_p ()) |
| return NULL; |
| |
| return riscv_get_arg_info (&info, cum, arg.mode, arg.type, arg.named, false); |
| } |
| |
| /* Implement TARGET_FUNCTION_ARG_ADVANCE. */ |
| |
| static void |
| riscv_function_arg_advance (cumulative_args_t cum_v, |
| const function_arg_info &arg) |
| { |
| CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); |
| struct riscv_arg_info info; |
| |
| riscv_get_arg_info (&info, cum, arg.mode, arg.type, arg.named, false); |
| |
| /* Advance the register count. This has the effect of setting |
| num_gprs to MAX_ARGS_IN_REGISTERS if a doubleword-aligned |
| argument required us to skip the final GPR and pass the whole |
| argument on the stack. */ |
| cum->num_fprs = info.fpr_offset + info.num_fprs; |
| cum->num_gprs = info.gpr_offset + info.num_gprs; |
| } |
| |
| /* Implement TARGET_ARG_PARTIAL_BYTES. */ |
| |
| static int |
| riscv_arg_partial_bytes (cumulative_args_t cum, |
| const function_arg_info &generic_arg) |
| { |
| struct riscv_arg_info arg; |
| |
| riscv_get_arg_info (&arg, get_cumulative_args (cum), generic_arg.mode, |
| generic_arg.type, generic_arg.named, false); |
| return arg.stack_p ? arg.num_gprs * UNITS_PER_WORD : 0; |
| } |
| |
| /* Implement FUNCTION_VALUE and LIBCALL_VALUE. For normal calls, |
| VALTYPE is the return type and MODE is VOIDmode. For libcalls, |
| VALTYPE is null and MODE is the mode of the return value. */ |
| |
| rtx |
| riscv_function_value (const_tree type, const_tree func, machine_mode mode) |
| { |
| struct riscv_arg_info info; |
| CUMULATIVE_ARGS args; |
| |
| if (type) |
| { |
| int unsigned_p = TYPE_UNSIGNED (type); |
| |
| mode = TYPE_MODE (type); |
| |
| /* Since TARGET_PROMOTE_FUNCTION_MODE unconditionally promotes, |
| return values, promote the mode here too. */ |
| mode = promote_function_mode (type, mode, &unsigned_p, func, 1); |
| } |
| |
| memset (&args, 0, sizeof args); |
| return riscv_get_arg_info (&info, &args, mode, type, true, true); |
| } |
| |
| /* Implement TARGET_PASS_BY_REFERENCE. */ |
| |
| static bool |
| riscv_pass_by_reference (cumulative_args_t cum_v, const function_arg_info &arg) |
| { |
| HOST_WIDE_INT size = arg.type_size_in_bytes (); |
| struct riscv_arg_info info; |
| CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); |
| |
| /* ??? std_gimplify_va_arg_expr passes NULL for cum. Fortunately, we |
| never pass variadic arguments in floating-point registers, so we can |
| avoid the call to riscv_get_arg_info in this case. */ |
| if (cum != NULL) |
| { |
| /* Don't pass by reference if we can use a floating-point register. */ |
| riscv_get_arg_info (&info, cum, arg.mode, arg.type, arg.named, false); |
| if (info.num_fprs) |
| return false; |
| } |
| |
| /* Pass by reference if the data do not fit in two integer registers. */ |
| return !IN_RANGE (size, 0, 2 * UNITS_PER_WORD); |
| } |
| |
| /* Implement TARGET_RETURN_IN_MEMORY. */ |
| |
| static bool |
| riscv_return_in_memory (const_tree type, const_tree fndecl ATTRIBUTE_UNUSED) |
| { |
| CUMULATIVE_ARGS args; |
| cumulative_args_t cum = pack_cumulative_args (&args); |
| |
| /* The rules for returning in memory are the same as for passing the |
| first named argument by reference. */ |
| memset (&args, 0, sizeof args); |
| function_arg_info arg (const_cast<tree> (type), /*named=*/true); |
| return riscv_pass_by_reference (cum, arg); |
| } |
| |
| /* Implement TARGET_SETUP_INCOMING_VARARGS. */ |
| |
| static void |
| riscv_setup_incoming_varargs (cumulative_args_t cum, |
| const function_arg_info &arg, |
| int *pretend_size ATTRIBUTE_UNUSED, int no_rtl) |
| { |
| CUMULATIVE_ARGS local_cum; |
| int gp_saved; |
| |
| /* The caller has advanced CUM up to, but not beyond, the last named |
| argument. Advance a local copy of CUM past the last "real" named |
| argument, to find out how many registers are left over. */ |
| local_cum = *get_cumulative_args (cum); |
| riscv_function_arg_advance (pack_cumulative_args (&local_cum), arg); |
| |
| /* Found out how many registers we need to save. */ |
| gp_saved = MAX_ARGS_IN_REGISTERS - local_cum.num_gprs; |
| |
| if (!no_rtl && gp_saved > 0) |
| { |
| rtx ptr = plus_constant (Pmode, virtual_incoming_args_rtx, |
| REG_PARM_STACK_SPACE (cfun->decl) |
| - gp_saved * UNITS_PER_WORD); |
| rtx mem = gen_frame_mem (BLKmode, ptr); |
| set_mem_alias_set (mem, get_varargs_alias_set ()); |
| |
| move_block_from_reg (local_cum.num_gprs + GP_ARG_FIRST, |
| mem, gp_saved); |
| } |
| if (REG_PARM_STACK_SPACE (cfun->decl) == 0) |
| cfun->machine->varargs_size = gp_saved * UNITS_PER_WORD; |
| } |
| |
| /* Handle an attribute requiring a FUNCTION_DECL; |
| arguments as in struct attribute_spec.handler. */ |
| static tree |
| riscv_handle_fndecl_attribute (tree *node, tree name, |
| tree args ATTRIBUTE_UNUSED, |
| int flags ATTRIBUTE_UNUSED, bool *no_add_attrs) |
| { |
| if (TREE_CODE (*node) != FUNCTION_DECL) |
| { |
| warning (OPT_Wattributes, "%qE attribute only applies to functions", |
| name); |
| *no_add_attrs = true; |
| } |
| |
| return NULL_TREE; |
| } |
| |
| /* Verify type based attributes. NODE is the what the attribute is being |
| applied to. NAME is the attribute name. ARGS are the attribute args. |
| FLAGS gives info about the context. NO_ADD_ATTRS should be set to true if |
| the attribute should be ignored. */ |
| |
| static tree |
| riscv_handle_type_attribute (tree *node ATTRIBUTE_UNUSED, tree name, tree args, |
| int flags ATTRIBUTE_UNUSED, bool *no_add_attrs) |
| { |
| /* Check for an argument. */ |
| if (is_attribute_p ("interrupt", name)) |
| { |
| if (args) |
| { |
| tree cst = TREE_VALUE (args); |
| const char *string; |
| |
| if (TREE_CODE (cst) != STRING_CST) |
| { |
| warning (OPT_Wattributes, |
| "%qE attribute requires a string argument", |
| name); |
| *no_add_attrs = true; |
| return NULL_TREE; |
| } |
| |
| string = TREE_STRING_POINTER (cst); |
| if (strcmp (string, "user") && strcmp (string, "supervisor") |
| && strcmp (string, "machine")) |
| { |
| warning (OPT_Wattributes, |
| "argument to %qE attribute is not \"user\", \"supervisor\", or \"machine\"", |
| name); |
| *no_add_attrs = true; |
| } |
| } |
| } |
| |
| return NULL_TREE; |
| } |
| |
| /* Return true if function TYPE is an interrupt function. */ |
| static bool |
| riscv_interrupt_type_p (tree type) |
| { |
| return lookup_attribute ("interrupt", TYPE_ATTRIBUTES (type)) != NULL; |
| } |
| |
| /* Return true if FUNC is a naked function. */ |
| static bool |
| riscv_naked_function_p (tree func) |
| { |
| tree func_decl = func; |
| if (func == NULL_TREE) |
| func_decl = current_function_decl; |
| return NULL_TREE != lookup_attribute ("naked", DECL_ATTRIBUTES (func_decl)); |
| } |
| |
| /* Implement TARGET_ALLOCATE_STACK_SLOTS_FOR_ARGS. */ |
| static bool |
| riscv_allocate_stack_slots_for_args () |
| { |
| /* Naked functions should not allocate stack slots for arguments. */ |
| return !riscv_naked_function_p (current_function_decl); |
| } |
| |
| /* Implement TARGET_WARN_FUNC_RETURN. */ |
| static bool |
| riscv_warn_func_return (tree decl) |
| { |
| /* Naked functions are implemented entirely in assembly, including the |
| return sequence, so suppress warnings about this. */ |
| return !riscv_naked_function_p (decl); |
| } |
| |
| /* Implement TARGET_EXPAND_BUILTIN_VA_START. */ |
| |
| static void |
| riscv_va_start (tree valist, rtx nextarg) |
| { |
| nextarg = plus_constant (Pmode, nextarg, -cfun->machine->varargs_size); |
| std_expand_builtin_va_start (valist, nextarg); |
| } |
| |
| /* Make ADDR suitable for use as a call or sibcall target. */ |
| |
| rtx |
| riscv_legitimize_call_address (rtx addr) |
| { |
| if (!call_insn_operand (addr, VOIDmode)) |
| |