| /* Subroutines used for LoongArch code generation. |
| Copyright (C) 2021-2022 Free Software Foundation, Inc. |
| Contributed by Loongson Ltd. |
| Based on MIPS and RISC-V 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 |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "target.h" |
| #include "rtl.h" |
| #include "tree.h" |
| #include "memmodel.h" |
| #include "gimple.h" |
| #include "cfghooks.h" |
| #include "df.h" |
| #include "tm_p.h" |
| #include "stringpool.h" |
| #include "attribs.h" |
| #include "optabs.h" |
| #include "regs.h" |
| #include "emit-rtl.h" |
| #include "recog.h" |
| #include "cgraph.h" |
| #include "diagnostic.h" |
| #include "insn-attr.h" |
| #include "output.h" |
| #include "alias.h" |
| #include "fold-const.h" |
| #include "varasm.h" |
| #include "stor-layout.h" |
| #include "calls.h" |
| #include "explow.h" |
| #include "expr.h" |
| #include "libfuncs.h" |
| #include "reload.h" |
| #include "common/common-target.h" |
| #include "langhooks.h" |
| #include "cfgrtl.h" |
| #include "cfganal.h" |
| #include "sched-int.h" |
| #include "gimplify.h" |
| #include "target-globals.h" |
| #include "tree-pass.h" |
| #include "context.h" |
| #include "builtins.h" |
| #include "rtl-iter.h" |
| #include "opts.h" |
| |
| /* This file should be included last. */ |
| #include "target-def.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 loongarch_symbol_type) (XINT (X, 1) - UNSPEC_ADDRESS_FIRST)) |
| |
| /* True if INSN is a loongarch.md pattern or asm statement. */ |
| /* ??? This test exists through the compiler, perhaps it should be |
| moved to rtl.h. */ |
| #define USEFUL_INSN_P(INSN) \ |
| (NONDEBUG_INSN_P (INSN) \ |
| && GET_CODE (PATTERN (INSN)) != USE \ |
| && GET_CODE (PATTERN (INSN)) != CLOBBER) |
| |
| /* True if bit BIT is set in VALUE. */ |
| #define BITSET_P(VALUE, BIT) (((VALUE) & (1 << (BIT))) != 0) |
| |
| /* Classifies an address. |
| |
| ADDRESS_REG |
| A natural register + offset address. The register satisfies |
| loongarch_valid_base_register_p and the offset is a const_arith_operand. |
| |
| ADDRESS_REG_REG |
| A base register indexed by (optionally scaled) register. |
| |
| 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 loongarch_address_type |
| { |
| ADDRESS_REG, |
| ADDRESS_REG_REG, |
| ADDRESS_LO_SUM, |
| ADDRESS_CONST_INT, |
| ADDRESS_SYMBOLIC |
| }; |
| |
| |
| /* Information about an address described by loongarch_address_type. */ |
| struct loongarch_address_info |
| { |
| enum loongarch_address_type type; |
| rtx reg; |
| rtx offset; |
| enum loongarch_symbol_type symbol_type; |
| }; |
| |
| /* Method of loading instant numbers: |
| |
| METHOD_NORMAL: |
| Load 0-31 bit of the immediate number. |
| |
| METHOD_LU32I: |
| Load 32-51 bit of the immediate number. |
| |
| METHOD_LU52I: |
| Load 52-63 bit of the immediate number. |
| |
| METHOD_INSV: |
| immediate like 0xfff00000fffffxxx |
| */ |
| enum loongarch_load_imm_method |
| { |
| METHOD_NORMAL, |
| METHOD_LU32I, |
| METHOD_LU52I, |
| METHOD_INSV |
| }; |
| |
| struct loongarch_integer_op |
| { |
| enum rtx_code code; |
| HOST_WIDE_INT value; |
| enum loongarch_load_imm_method method; |
| }; |
| |
| /* The largest number of operations needed to load an integer constant. |
| The worst accepted case for 64-bit constants is LU12I.W,LU32I.D,LU52I.D,ORI |
| or LU12I.W,LU32I.D,LU52I.D,ADDI.D DECL_ASSEMBLER_NAME. */ |
| #define LARCH_MAX_INTEGER_OPS 4 |
| |
| /* Arrays that map GCC register numbers to debugger register numbers. */ |
| int loongarch_dwarf_regno[FIRST_PSEUDO_REGISTER]; |
| |
| /* Index [M][R] is true if register R is allowed to hold a value of mode M. */ |
| static bool loongarch_hard_regno_mode_ok_p[MAX_MACHINE_MODE] |
| [FIRST_PSEUDO_REGISTER]; |
| |
| /* Index C is true if character C is a valid PRINT_OPERAND punctation |
| character. */ |
| static bool loongarch_print_operand_punct[256]; |
| |
| /* Cached value of can_issue_more. This is cached in loongarch_variable_issue |
| hook and returned from loongarch_sched_reorder2. */ |
| static int cached_can_issue_more; |
| |
| /* Index R is the smallest register class that contains register R. */ |
| const enum reg_class loongarch_regno_to_class[FIRST_PSEUDO_REGISTER] = { |
| GR_REGS, GR_REGS, GR_REGS, GR_REGS, |
| JIRL_REGS, JIRL_REGS, JIRL_REGS, JIRL_REGS, |
| JIRL_REGS, JIRL_REGS, JIRL_REGS, JIRL_REGS, |
| SIBCALL_REGS, JIRL_REGS, SIBCALL_REGS, SIBCALL_REGS, |
| SIBCALL_REGS, SIBCALL_REGS, SIBCALL_REGS, SIBCALL_REGS, |
| SIBCALL_REGS, GR_REGS, GR_REGS, JIRL_REGS, |
| JIRL_REGS, JIRL_REGS, JIRL_REGS, JIRL_REGS, |
| JIRL_REGS, JIRL_REGS, JIRL_REGS, JIRL_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, |
| FCC_REGS, FCC_REGS, FCC_REGS, FCC_REGS, |
| FCC_REGS, FCC_REGS, FCC_REGS, FCC_REGS, |
| FRAME_REGS, FRAME_REGS |
| }; |
| |
| /* Which cost information to use. */ |
| static const struct loongarch_rtx_cost_data *loongarch_cost; |
| |
| /* Information about a single argument. */ |
| struct loongarch_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; |
| }; |
| |
| /* Invoke MACRO (COND) for each fcmp.cond.{s/d} condition. */ |
| #define LARCH_FP_CONDITIONS(MACRO) \ |
| MACRO (f), \ |
| MACRO (un), \ |
| MACRO (eq), \ |
| MACRO (ueq), \ |
| MACRO (olt), \ |
| MACRO (ult), \ |
| MACRO (ole), \ |
| MACRO (ule), \ |
| MACRO (sf), \ |
| MACRO (ngle), \ |
| MACRO (seq), \ |
| MACRO (ngl), \ |
| MACRO (lt), \ |
| MACRO (nge), \ |
| MACRO (le), \ |
| MACRO (ngt) |
| |
| /* Enumerates the codes above as LARCH_FP_COND_<X>. */ |
| #define DECLARE_LARCH_COND(X) LARCH_FP_COND_##X |
| enum loongarch_fp_condition |
| { |
| LARCH_FP_CONDITIONS (DECLARE_LARCH_COND) |
| }; |
| #undef DECLARE_LARCH_COND |
| |
| /* Index X provides the string representation of LARCH_FP_COND_<X>. */ |
| #define STRINGIFY(X) #X |
| const char *const |
| loongarch_fp_conditions[16]= {LARCH_FP_CONDITIONS (STRINGIFY)}; |
| #undef STRINGIFY |
| |
| /* 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 |
| loongarch_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 |
| loongarch_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; |
| } loongarch_aggregate_field; |
| |
| /* Identify subfields of aggregates that are candidates for passing in |
| floating-point registers. */ |
| |
| static int |
| loongarch_flatten_aggregate_field (const_tree type, |
| loongarch_aggregate_field fields[2], int n, |
| HOST_WIDE_INT offset) |
| { |
| 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; |
| |
| if (DECL_SIZE (f) && integer_zerop (DECL_SIZE (f))) |
| continue; |
| |
| HOST_WIDE_INT pos = offset + int_byte_position (f); |
| n = loongarch_flatten_aggregate_field (TREE_TYPE (f), fields, n, |
| pos); |
| if (n < 0) |
| return -1; |
| } |
| return n; |
| |
| case ARRAY_TYPE: |
| { |
| HOST_WIDE_INT n_elts; |
| loongarch_aggregate_field subfields[2]; |
| tree index = TYPE_DOMAIN (type); |
| tree elt_size = TYPE_SIZE_UNIT (TREE_TYPE (type)); |
| int n_subfields = loongarch_flatten_aggregate_field (TREE_TYPE (type), |
| subfields, 0, |
| offset); |
| |
| /* 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 |
| loongarch_flatten_aggregate_argument (const_tree type, |
| loongarch_aggregate_field fields[2]) |
| { |
| if (!type || TREE_CODE (type) != RECORD_TYPE) |
| return -1; |
| |
| return loongarch_flatten_aggregate_field (type, fields, 0, 0); |
| } |
| |
| /* 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 |
| loongarch_pass_aggregate_num_fpr (const_tree type, |
| loongarch_aggregate_field fields[2]) |
| { |
| int n = loongarch_flatten_aggregate_argument (type, fields); |
| |
| for (int i = 0; i < n; i++) |
| if (!SCALAR_FLOAT_TYPE_P (fields[i].type)) |
| return 0; |
| |
| return n > 0 ? n : 0; |
| } |
| |
| /* See whether TYPE is a record whose fields should be returned in one |
| floating-point register and one integer register. If so, populate |
| FIELDS accordingly. */ |
| |
| static bool |
| loongarch_pass_aggregate_in_fpr_and_gpr_p (const_tree type, |
| loongarch_aggregate_field fields[2]) |
| { |
| unsigned num_int = 0, num_float = 0; |
| int n = loongarch_flatten_aggregate_argument (type, fields); |
| |
| for (int i = 0; i < n; i++) |
| { |
| num_float += SCALAR_FLOAT_TYPE_P (fields[i].type); |
| num_int += INTEGRAL_TYPE_P (fields[i].type); |
| } |
| |
| return num_int == 1 && num_float == 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 |
| loongarch_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 |
| loongarch_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 |
| loongarch_get_arg_info (struct loongarch_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 = loongarch_function_arg_boundary (mode, type); |
| |
| memset (info, 0, sizeof (*info)); |
| info->gpr_offset = cum->num_gprs; |
| info->fpr_offset = cum->num_fprs; |
| |
| if (named) |
| { |
| loongarch_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 |
| = loongarch_pass_aggregate_num_fpr (type, fields)) |
| && info->fpr_offset + info->num_fprs <= MAX_ARGS_IN_REGISTERS) |
| switch (info->num_fprs) |
| { |
| case 1: |
| return loongarch_pass_fpr_single (mode, fregno, |
| TYPE_MODE (fields[0].type), |
| fields[0].offset); |
| |
| case 2: |
| return loongarch_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 = loongarch_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 loongarch_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 (loongarch_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 loongarch_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 |
| loongarch_function_arg (cumulative_args_t cum_v, const function_arg_info &arg) |
| { |
| CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); |
| struct loongarch_arg_info info; |
| |
| if (arg.end_marker_p ()) |
| return NULL; |
| |
| return loongarch_get_arg_info (&info, cum, arg.mode, arg.type, arg.named, |
| false); |
| } |
| |
| /* Implement TARGET_FUNCTION_ARG_ADVANCE. */ |
| |
| static void |
| loongarch_function_arg_advance (cumulative_args_t cum_v, |
| const function_arg_info &arg) |
| { |
| CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); |
| struct loongarch_arg_info info; |
| |
| loongarch_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 |
| loongarch_arg_partial_bytes (cumulative_args_t cum, |
| const function_arg_info &generic_arg) |
| { |
| struct loongarch_arg_info arg; |
| |
| loongarch_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. */ |
| |
| static rtx |
| loongarch_function_value_1 (const_tree type, const_tree func, |
| machine_mode mode) |
| { |
| struct loongarch_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 loongarch_get_arg_info (&info, &args, mode, type, true, true); |
| } |
| |
| |
| /* Implement TARGET_FUNCTION_VALUE. */ |
| |
| static rtx |
| loongarch_function_value (const_tree valtype, const_tree fn_decl_or_type, |
| bool outgoing ATTRIBUTE_UNUSED) |
| { |
| return loongarch_function_value_1 (valtype, fn_decl_or_type, VOIDmode); |
| } |
| |
| /* Implement TARGET_LIBCALL_VALUE. */ |
| |
| static rtx |
| loongarch_libcall_value (machine_mode mode, const_rtx fun ATTRIBUTE_UNUSED) |
| { |
| return loongarch_function_value_1 (NULL_TREE, NULL_TREE, mode); |
| } |
| |
| |
| /* Implement TARGET_PASS_BY_REFERENCE. */ |
| |
| static bool |
| loongarch_pass_by_reference (cumulative_args_t cum_v, |
| const function_arg_info &arg) |
| { |
| HOST_WIDE_INT size = arg.type_size_in_bytes (); |
| struct loongarch_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 loongarch_get_arg_info in this case. */ |
| if (cum != NULL) |
| { |
| /* Don't pass by reference if we can use a floating-point register. */ |
| loongarch_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 |
| loongarch_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 loongarch_pass_by_reference (cum, arg); |
| } |
| |
| /* Implement TARGET_SETUP_INCOMING_VARARGS. */ |
| |
| static void |
| loongarch_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); |
| if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl))) |
| loongarch_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; |
| } |
| |
| /* Make the last instruction frame-related and note that it performs |
| the operation described by FRAME_PATTERN. */ |
| |
| static void |
| loongarch_set_frame_expr (rtx frame_pattern) |
| { |
| rtx insn; |
| |
| insn = get_last_insn (); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| REG_NOTES (insn) = alloc_EXPR_LIST (REG_FRAME_RELATED_EXPR, frame_pattern, |
| REG_NOTES (insn)); |
| } |
| |
| /* Return a frame-related rtx that stores REG at MEM. |
| REG must be a single register. */ |
| |
| static rtx |
| loongarch_frame_set (rtx mem, rtx reg) |
| { |
| rtx set = gen_rtx_SET (mem, reg); |
| RTX_FRAME_RELATED_P (set) = 1; |
| return set; |
| } |
| |
| /* Return true if the current function must save register REGNO. */ |
| |
| static bool |
| loongarch_save_reg_p (unsigned int regno) |
| { |
| bool call_saved = !global_regs[regno] && !call_used_regs[regno]; |
| bool might_clobber |
| = crtl->saves_all_registers || df_regs_ever_live_p (regno); |
| |
| if (call_saved && might_clobber) |
| return true; |
| |
| if (regno == HARD_FRAME_POINTER_REGNUM && frame_pointer_needed) |
| return true; |
| |
| if (regno == RETURN_ADDR_REGNUM && crtl->calls_eh_return) |
| return true; |
| |
| return false; |
| } |
| |
| /* Determine which GPR save/restore routine to call. */ |
| |
| static unsigned |
| loongarch_save_libcall_count (unsigned mask) |
| { |
| for (unsigned n = GP_REG_LAST; n > GP_REG_FIRST; n--) |
| if (BITSET_P (mask, n)) |
| return CALLEE_SAVED_REG_NUMBER (n) + 1; |
| abort (); |
| } |
| |
| /* Populate the current function's loongarch_frame_info structure. |
| |
| LoongArch stack frames grown downward. High addresses are at the top. |
| |
| +-------------------------------+ |
| | | |
| | incoming stack arguments | |
| | | |
| +-------------------------------+ <-- incoming stack pointer |
| | | |
| | callee-allocated save area | |
| | for arguments that are | |
| | split between registers and | |
| | the stack | |
| | | |
| +-------------------------------+ <-- arg_pointer_rtx (virtual) |
| | | |
| | callee-allocated save area | |
| | for register varargs | |
| | | |
| +-------------------------------+ <-- hard_frame_pointer_rtx; |
| | | stack_pointer_rtx + gp_sp_offset |
| | GPR save area | + UNITS_PER_WORD |
| | | |
| +-------------------------------+ <-- stack_pointer_rtx + fp_sp_offset |
| | | + UNITS_PER_HWVALUE |
| | FPR save area | |
| | | |
| +-------------------------------+ <-- frame_pointer_rtx (virtual) |
| | | |
| | local variables | |
| | | |
| P +-------------------------------+ |
| | | |
| | outgoing stack arguments | |
| | | |
| +-------------------------------+ <-- stack_pointer_rtx |
| |
| Dynamic stack allocations such as alloca insert data at point P. |
| They decrease stack_pointer_rtx but leave frame_pointer_rtx and |
| hard_frame_pointer_rtx unchanged. */ |
| |
| static void |
| loongarch_compute_frame_info (void) |
| { |
| struct loongarch_frame_info *frame; |
| HOST_WIDE_INT offset; |
| unsigned int regno, i, num_x_saved = 0, num_f_saved = 0; |
| |
| frame = &cfun->machine->frame; |
| memset (frame, 0, sizeof (*frame)); |
| |
| /* Find out which GPRs we need to save. */ |
| for (regno = GP_REG_FIRST; regno <= GP_REG_LAST; regno++) |
| if (loongarch_save_reg_p (regno)) |
| frame->mask |= 1 << (regno - GP_REG_FIRST), num_x_saved++; |
| |
| /* If this function calls eh_return, we must also save and restore the |
| EH data registers. */ |
| if (crtl->calls_eh_return) |
| for (i = 0; (regno = EH_RETURN_DATA_REGNO (i)) != INVALID_REGNUM; i++) |
| frame->mask |= 1 << (regno - GP_REG_FIRST), num_x_saved++; |
| |
| /* Find out which FPRs we need to save. This loop must iterate over |
| the same space as its companion in loongarch_for_each_saved_reg. */ |
| if (TARGET_HARD_FLOAT) |
| for (regno = FP_REG_FIRST; regno <= FP_REG_LAST; regno++) |
| if (loongarch_save_reg_p (regno)) |
| frame->fmask |= 1 << (regno - FP_REG_FIRST), num_f_saved++; |
| |
| /* At the bottom of the frame are any outgoing stack arguments. */ |
| offset = LARCH_STACK_ALIGN (crtl->outgoing_args_size); |
| /* Next are local stack variables. */ |
| offset += LARCH_STACK_ALIGN (get_frame_size ()); |
| /* The virtual frame pointer points above the local variables. */ |
| frame->frame_pointer_offset = offset; |
| /* Next are the callee-saved FPRs. */ |
| if (frame->fmask) |
| { |
| offset += LARCH_STACK_ALIGN (num_f_saved * UNITS_PER_FP_REG); |
| frame->fp_sp_offset = offset - UNITS_PER_FP_REG; |
| } |
| else |
| frame->fp_sp_offset = offset; |
| /* Next are the callee-saved GPRs. */ |
| if (frame->mask) |
| { |
| unsigned x_save_size = LARCH_STACK_ALIGN (num_x_saved * UNITS_PER_WORD); |
| unsigned num_save_restore |
| = 1 + loongarch_save_libcall_count (frame->mask); |
| |
| /* Only use save/restore routines if they don't alter the stack size. */ |
| if (LARCH_STACK_ALIGN (num_save_restore * UNITS_PER_WORD) == x_save_size) |
| frame->save_libcall_adjustment = x_save_size; |
| |
| offset += x_save_size; |
| frame->gp_sp_offset = offset - UNITS_PER_WORD; |
| } |
| else |
| frame->gp_sp_offset = offset; |
| /* The hard frame pointer points above the callee-saved GPRs. */ |
| frame->hard_frame_pointer_offset = offset; |
| /* Above the hard frame pointer is the callee-allocated varags save area. */ |
| offset += LARCH_STACK_ALIGN (cfun->machine->varargs_size); |
| /* Next is the callee-allocated area for pretend stack arguments. */ |
| offset += LARCH_STACK_ALIGN (crtl->args.pretend_args_size); |
| /* Arg pointer must be below pretend args, but must be above alignment |
| padding. */ |
| frame->arg_pointer_offset = offset - crtl->args.pretend_args_size; |
| frame->total_size = offset; |
| /* Next points the incoming stack pointer and any incoming arguments. */ |
| |
| /* Only use save/restore routines when the GPRs are atop the frame. */ |
| if (frame->hard_frame_pointer_offset != frame->total_size) |
| frame->save_libcall_adjustment = 0; |
| } |
| |
| /* Implement INITIAL_ELIMINATION_OFFSET. FROM is either the frame pointer |
| or argument pointer. TO is either the stack pointer or hard frame |
| pointer. */ |
| |
| HOST_WIDE_INT |
| loongarch_initial_elimination_offset (int from, int to) |
| { |
| HOST_WIDE_INT src, dest; |
| |
| loongarch_compute_frame_info (); |
| |
| if (to == HARD_FRAME_POINTER_REGNUM) |
| dest = cfun->machine->frame.hard_frame_pointer_offset; |
| else if (to == STACK_POINTER_REGNUM) |
| dest = 0; /* The stack pointer is the base of all offsets, hence 0. */ |
| else |
| gcc_unreachable (); |
| |
| if (from == FRAME_POINTER_REGNUM) |
| src = cfun->machine->frame.frame_pointer_offset; |
| else if (from == ARG_POINTER_REGNUM) |
| src = cfun->machine->frame.arg_pointer_offset; |
| else |
| gcc_unreachable (); |
| |
| return src - dest; |
| } |
| |
| /* A function to save or store a register. The first argument is the |
| register and the second is the stack slot. */ |
| typedef void (*loongarch_save_restore_fn) (rtx, rtx); |
| |
| /* Use FN to save or restore register REGNO. MODE is the register's |
| mode and OFFSET is the offset of its save slot from the current |
| stack pointer. */ |
| |
| static void |
| loongarch_save_restore_reg (machine_mode mode, int regno, HOST_WIDE_INT offset, |
| loongarch_save_restore_fn fn) |
| { |
| rtx mem; |
| |
| mem = gen_frame_mem (mode, plus_constant (Pmode, stack_pointer_rtx, offset)); |
| fn (gen_rtx_REG (mode, regno), mem); |
| } |
| |
| /* Call FN for each register that is saved by the current function. |
| SP_OFFSET is the offset of the current stack pointer from the start |
| of the frame. */ |
| |
| static void |
| loongarch_for_each_saved_reg (HOST_WIDE_INT sp_offset, |
| loongarch_save_restore_fn fn) |
| { |
| HOST_WIDE_INT offset; |
| |
| /* Save the link register and s-registers. */ |
| offset = cfun->machine->frame.gp_sp_offset - sp_offset; |
| for (int regno = GP_REG_FIRST; regno <= GP_REG_LAST; regno++) |
| if (BITSET_P (cfun->machine->frame.mask, regno - GP_REG_FIRST)) |
| { |
| loongarch_save_restore_reg (word_mode, regno, offset, fn); |
| offset -= UNITS_PER_WORD; |
| } |
| |
| /* This loop must iterate over the same space as its companion in |
| loongarch_compute_frame_info. */ |
| offset = cfun->machine->frame.fp_sp_offset - sp_offset; |
| for (int regno = FP_REG_FIRST; regno <= FP_REG_LAST; regno++) |
| if (BITSET_P (cfun->machine->frame.fmask, regno - FP_REG_FIRST)) |
| { |
| machine_mode mode = TARGET_DOUBLE_FLOAT ? DFmode : SFmode; |
| |
| loongarch_save_restore_reg (mode, regno, offset, fn); |
| offset -= GET_MODE_SIZE (mode); |
| } |
| } |
| |
| /* 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 |
| loongarch_emit_move (rtx dest, rtx src) |
| { |
| return (can_create_pseudo_p () ? emit_move_insn (dest, src) |
| : emit_move_insn_1 (dest, src)); |
| } |
| |
| /* Save register REG to MEM. Make the instruction frame-related. */ |
| |
| static void |
| loongarch_save_reg (rtx reg, rtx mem) |
| { |
| loongarch_emit_move (mem, reg); |
| loongarch_set_frame_expr (loongarch_frame_set (mem, reg)); |
| } |
| |
| /* Restore register REG from MEM. */ |
| |
| static void |
| loongarch_restore_reg (rtx reg, rtx mem) |
| { |
| rtx insn = loongarch_emit_move (reg, mem); |
| rtx dwarf = NULL_RTX; |
| dwarf = alloc_reg_note (REG_CFA_RESTORE, reg, dwarf); |
| REG_NOTES (insn) = dwarf; |
| |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| |
| /* For stack frames that can't be allocated with a single ADDI instruction, |
| compute the best value to initially allocate. It must at a minimum |
| allocate enough space to spill the callee-saved registers. */ |
| |
| static HOST_WIDE_INT |
| loongarch_first_stack_step (struct loongarch_frame_info *frame) |
| { |
| if (IMM12_OPERAND (frame->total_size)) |
| return frame->total_size; |
| |
| HOST_WIDE_INT min_first_step |
| = LARCH_STACK_ALIGN (frame->total_size - frame->fp_sp_offset); |
| HOST_WIDE_INT max_first_step = IMM_REACH / 2 - PREFERRED_STACK_BOUNDARY / 8; |
| HOST_WIDE_INT min_second_step = frame->total_size - max_first_step; |
| gcc_assert (min_first_step <= max_first_step); |
| |
| /* As an optimization, use the least-significant bits of the total frame |
| size, so that the second adjustment step is just LU12I + ADD. */ |
| if (!IMM12_OPERAND (min_second_step) |
| && frame->total_size % IMM_REACH < IMM_REACH / 2 |
| && frame->total_size % IMM_REACH >= min_first_step) |
| return frame->total_size % IMM_REACH; |
| |
| return max_first_step; |
| } |
| |
| static void |
| loongarch_emit_stack_tie (void) |
| { |
| emit_insn (gen_stack_tie (Pmode, stack_pointer_rtx, hard_frame_pointer_rtx)); |
| } |
| |
| #define PROBE_INTERVAL (1 << STACK_CHECK_PROBE_INTERVAL_EXP) |
| |
| #if PROBE_INTERVAL > 16384 |
| #error Cannot use indexed addressing mode for stack probing |
| #endif |
| |
| /* Emit code to probe a range of stack addresses from FIRST to FIRST+SIZE, |
| inclusive. These are offsets from the current stack pointer. */ |
| |
| static void |
| loongarch_emit_probe_stack_range (HOST_WIDE_INT first, HOST_WIDE_INT size) |
| { |
| /* See if we have a constant small number of probes to generate. If so, |
| that's the easy case. */ |
| if ((TARGET_64BIT && (first + size <= 32768)) |
| || (!TARGET_64BIT && (first + size <= 2048))) |
| { |
| HOST_WIDE_INT i; |
| |
| /* Probe at FIRST + N * PROBE_INTERVAL for values of N from 1 until |
| it exceeds SIZE. If only one probe is needed, this will not |
| generate any code. Then probe at FIRST + SIZE. */ |
| for (i = PROBE_INTERVAL; i < size; i += PROBE_INTERVAL) |
| emit_stack_probe (plus_constant (Pmode, stack_pointer_rtx, |
| -(first + i))); |
| |
| emit_stack_probe (plus_constant (Pmode, stack_pointer_rtx, |
| -(first + size))); |
| } |
| |
| /* Otherwise, do the same as above, but in a loop. Note that we must be |
| extra careful with variables wrapping around because we might be at |
| the very top (or the very bottom) of the address space and we have |
| to be able to handle this case properly; in particular, we use an |
| equality test for the loop condition. */ |
| else |
| { |
| HOST_WIDE_INT rounded_size; |
| rtx r13 = LARCH_PROLOGUE_TEMP (Pmode); |
| rtx r12 = LARCH_PROLOGUE_TEMP2 (Pmode); |
| rtx r14 = LARCH_PROLOGUE_TEMP3 (Pmode); |
| |
| /* Sanity check for the addressing mode we're going to use. */ |
| gcc_assert (first <= 16384); |
| |
| |
| /* Step 1: round SIZE to the previous multiple of the interval. */ |
| |
| rounded_size = ROUND_DOWN (size, PROBE_INTERVAL); |
| |
| /* TEST_ADDR = SP + FIRST */ |
| if (first != 0) |
| { |
| emit_move_insn (r14, GEN_INT (first)); |
| emit_insn (gen_rtx_SET (r13, gen_rtx_MINUS (Pmode, |
| stack_pointer_rtx, |
| r14))); |
| } |
| else |
| emit_move_insn (r13, stack_pointer_rtx); |
| |
| /* Step 2: compute initial and final value of the loop counter. */ |
| |
| emit_move_insn (r14, GEN_INT (PROBE_INTERVAL)); |
| /* LAST_ADDR = SP + FIRST + ROUNDED_SIZE. */ |
| if (rounded_size == 0) |
| emit_move_insn (r12, r13); |
| else |
| { |
| emit_move_insn (r12, GEN_INT (rounded_size)); |
| emit_insn (gen_rtx_SET (r12, gen_rtx_MINUS (Pmode, r13, r12))); |
| /* Step 3: the loop |
| |
| do |
| { |
| TEST_ADDR = TEST_ADDR + PROBE_INTERVAL |
| probe at TEST_ADDR |
| } |
| while (TEST_ADDR != LAST_ADDR) |
| |
| probes at FIRST + N * PROBE_INTERVAL for values of N from 1 |
| until it is equal to ROUNDED_SIZE. */ |
| |
| emit_insn (gen_probe_stack_range (Pmode, r13, r13, r12, r14)); |
| } |
| |
| /* Step 4: probe at FIRST + SIZE if we cannot assert at compile-time |
| that SIZE is equal to ROUNDED_SIZE. */ |
| |
| if (size != rounded_size) |
| { |
| if (TARGET_64BIT) |
| emit_stack_probe (plus_constant (Pmode, r12, rounded_size - size)); |
| else |
| { |
| HOST_WIDE_INT i; |
| for (i = 2048; i < (size - rounded_size); i += 2048) |
| { |
| emit_stack_probe (plus_constant (Pmode, r12, -i)); |
| emit_insn (gen_rtx_SET (r12, |
| plus_constant (Pmode, r12, -2048))); |
| } |
| rtx r1 = plus_constant (Pmode, r12, |
| -(size - rounded_size - i + 2048)); |
| emit_stack_probe (r1); |
| } |
| } |
| } |
| |
| /* Make sure nothing is scheduled before we are done. */ |
| emit_insn (gen_blockage ()); |
| } |
| |
| /* Probe a range of stack addresses from REG1 to REG2 inclusive. These are |
| absolute addresses. */ |
| const char * |
| loongarch_output_probe_stack_range (rtx reg1, rtx reg2, rtx reg3) |
| { |
| static int labelno = 0; |
| char loop_lab[32], tmp[64]; |
| rtx xops[3]; |
| |
| ASM_GENERATE_INTERNAL_LABEL (loop_lab, "LPSRL", labelno++); |
| |
| /* Loop. */ |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, loop_lab); |
| |
| /* TEST_ADDR = TEST_ADDR + PROBE_INTERVAL. */ |
| xops[0] = reg1; |
| xops[1] = GEN_INT (-PROBE_INTERVAL); |
| xops[2] = reg3; |
| if (TARGET_64BIT) |
| output_asm_insn ("sub.d\t%0,%0,%2", xops); |
| else |
| output_asm_insn ("sub.w\t%0,%0,%2", xops); |
| |
| /* Probe at TEST_ADDR, test if TEST_ADDR == LAST_ADDR and branch. */ |
| xops[1] = reg2; |
| strcpy (tmp, "bne\t%0,%1,"); |
| if (TARGET_64BIT) |
| output_asm_insn ("st.d\t$r0,%0,0", xops); |
| else |
| output_asm_insn ("st.w\t$r0,%0,0", xops); |
| output_asm_insn (strcat (tmp, &loop_lab[1]), xops); |
| |
| return ""; |
| } |
| |
| /* Expand the "prologue" pattern. */ |
| |
| void |
| loongarch_expand_prologue (void) |
| { |
| struct loongarch_frame_info *frame = &cfun->machine->frame; |
| HOST_WIDE_INT size = frame->total_size; |
| HOST_WIDE_INT tmp; |
| rtx insn; |
| |
| if (flag_stack_usage_info) |
| current_function_static_stack_size = size; |
| |
| if (flag_stack_check == STATIC_BUILTIN_STACK_CHECK |
| || flag_stack_clash_protection) |
| { |
| if (crtl->is_leaf && !cfun->calls_alloca) |
| { |
| if (size > PROBE_INTERVAL && size > get_stack_check_protect ()) |
| { |
| tmp = size - get_stack_check_protect (); |
| loongarch_emit_probe_stack_range (get_stack_check_protect (), |
| tmp); |
| } |
| } |
| else if (size > 0) |
| loongarch_emit_probe_stack_range (get_stack_check_protect (), size); |
| } |
| |
| /* Save the registers. */ |
| if ((frame->mask | frame->fmask) != 0) |
| { |
| HOST_WIDE_INT step1 = MIN (size, loongarch_first_stack_step (frame)); |
| |
| insn = gen_add3_insn (stack_pointer_rtx, stack_pointer_rtx, |
| GEN_INT (-step1)); |
| RTX_FRAME_RELATED_P (emit_insn (insn)) = 1; |
| size -= step1; |
| loongarch_for_each_saved_reg (size, loongarch_save_reg); |
| } |
| |
| |
| /* Set up the frame pointer, if we're using one. */ |
| if (frame_pointer_needed) |
| { |
| insn = gen_add3_insn (hard_frame_pointer_rtx, stack_pointer_rtx, |
| GEN_INT (frame->hard_frame_pointer_offset - size)); |
| RTX_FRAME_RELATED_P (emit_insn (insn)) = 1; |
| |
| loongarch_emit_stack_tie (); |
| } |
| |
| /* Allocate the rest of the frame. */ |
| if (size > 0) |
| { |
| if (IMM12_OPERAND (-size)) |
| { |
| insn = gen_add3_insn (stack_pointer_rtx, stack_pointer_rtx, |
| GEN_INT (-size)); |
| RTX_FRAME_RELATED_P (emit_insn (insn)) = 1; |
| } |
| else |
| { |
| loongarch_emit_move (LARCH_PROLOGUE_TEMP (Pmode), GEN_INT (-size)); |
| emit_insn (gen_add3_insn (stack_pointer_rtx, stack_pointer_rtx, |
| LARCH_PROLOGUE_TEMP (Pmode))); |
| |
| /* Describe the effect of the previous instructions. */ |
| insn = plus_constant (Pmode, stack_pointer_rtx, -size); |
| insn = gen_rtx_SET (stack_pointer_rtx, insn); |
| loongarch_set_frame_expr (insn); |
| } |
| } |
| } |
| |
| /* Return nonzero if this function is known to have a null epilogue. |
| This allows the optimizer to omit jumps to jumps if no stack |
| was created. */ |
| |
| bool |
| loongarch_can_use_return_insn (void) |
| { |
| return reload_completed && cfun->machine->frame.total_size == 0; |
| } |
| |
| /* Expand an "epilogue" or "sibcall_epilogue" pattern; SIBCALL_P |
| says which. */ |
| |
| void |
| loongarch_expand_epilogue (bool sibcall_p) |
| { |
| /* Split the frame into two. STEP1 is the amount of stack we should |
| deallocate before restoring the registers. STEP2 is the amount we |
| should deallocate afterwards. |
| |
| Start off by assuming that no registers need to be restored. */ |
| struct loongarch_frame_info *frame = &cfun->machine->frame; |
| HOST_WIDE_INT step1 = frame->total_size; |
| HOST_WIDE_INT step2 = 0; |
| rtx ra = gen_rtx_REG (Pmode, RETURN_ADDR_REGNUM); |
| rtx insn; |
| |
| /* We need to add memory barrier to prevent read from deallocated stack. */ |
| bool need_barrier_p |
| = (get_frame_size () + cfun->machine->frame.arg_pointer_offset) != 0; |
| |
| if (!sibcall_p && loongarch_can_use_return_insn ()) |
| { |
| emit_jump_insn (gen_return ()); |
| return; |
| } |
| |
| /* Move past any dynamic stack allocations. */ |
| if (cfun->calls_alloca) |
| { |
| /* Emit a barrier to prevent loads from a deallocated stack. */ |
| loongarch_emit_stack_tie (); |
| need_barrier_p = false; |
| |
| rtx adjust = GEN_INT (-frame->hard_frame_pointer_offset); |
| if (!IMM12_OPERAND (INTVAL (adjust))) |
| { |
| loongarch_emit_move (LARCH_PROLOGUE_TEMP (Pmode), adjust); |
| adjust = LARCH_PROLOGUE_TEMP (Pmode); |
| } |
| |
| insn = emit_insn (gen_add3_insn (stack_pointer_rtx, |
| hard_frame_pointer_rtx, |
| adjust)); |
| |
| rtx dwarf = NULL_RTX; |
| rtx minus_offset = GEN_INT (-frame->hard_frame_pointer_offset); |
| rtx cfa_adjust_value = gen_rtx_PLUS (Pmode, |
| hard_frame_pointer_rtx, |
| minus_offset); |
| |
| rtx cfa_adjust_rtx = gen_rtx_SET (stack_pointer_rtx, cfa_adjust_value); |
| dwarf = alloc_reg_note (REG_CFA_ADJUST_CFA, cfa_adjust_rtx, dwarf); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| |
| REG_NOTES (insn) = dwarf; |
| } |
| |
| /* If we need to restore registers, deallocate as much stack as |
| possible in the second step without going out of range. */ |
| if ((frame->mask | frame->fmask) != 0) |
| { |
| step2 = loongarch_first_stack_step (frame); |
| step1 -= step2; |
| } |
| |
| /* Set TARGET to BASE + STEP1. */ |
| if (step1 > 0) |
| { |
| /* Emit a barrier to prevent loads from a deallocated stack. */ |
| loongarch_emit_stack_tie (); |
| need_barrier_p = false; |
| |
| /* Get an rtx for STEP1 that we can add to BASE. */ |
| rtx adjust = GEN_INT (step1); |
| if (!IMM12_OPERAND (step1)) |
| { |
| loongarch_emit_move (LARCH_PROLOGUE_TEMP (Pmode), adjust); |
| adjust = LARCH_PROLOGUE_TEMP (Pmode); |
| } |
| |
| insn = emit_insn (gen_add3_insn (stack_pointer_rtx, |
| stack_pointer_rtx, |
| adjust)); |
| |
| rtx dwarf = NULL_RTX; |
| rtx cfa_adjust_rtx = gen_rtx_PLUS (Pmode, stack_pointer_rtx, |
| GEN_INT (step2)); |
| |
| dwarf = alloc_reg_note (REG_CFA_DEF_CFA, cfa_adjust_rtx, dwarf); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| |
| REG_NOTES (insn) = dwarf; |
| } |
| |
| /* Restore the registers. */ |
| loongarch_for_each_saved_reg (frame->total_size - step2, |
| loongarch_restore_reg); |
| |
| if (need_barrier_p) |
| loongarch_emit_stack_tie (); |
| |
| /* Deallocate the final bit of the frame. */ |
| if (step2 > 0) |
| { |
| insn = emit_insn (gen_add3_insn (stack_pointer_rtx, |
| stack_pointer_rtx, |
| GEN_INT (step2))); |
| |
| rtx dwarf = NULL_RTX; |
| rtx cfa_adjust_rtx = gen_rtx_PLUS (Pmode, stack_pointer_rtx, const0_rtx); |
| dwarf = alloc_reg_note (REG_CFA_DEF_CFA, cfa_adjust_rtx, dwarf); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| |
| REG_NOTES (insn) = dwarf; |
| } |
| |
| /* Add in the __builtin_eh_return stack adjustment. */ |
| if (crtl->calls_eh_return) |
| emit_insn (gen_add3_insn (stack_pointer_rtx, stack_pointer_rtx, |
| EH_RETURN_STACKADJ_RTX)); |
| |
| if (!sibcall_p) |
| emit_jump_insn (gen_simple_return_internal (ra)); |
| } |
| |
| #define LU32I_B (0xfffffULL << 32) |
| #define LU52I_B (0xfffULL << 52) |
| |
| /* Fill CODES with a sequence of rtl operations to load VALUE. |
| Return the number of operations needed. */ |
| |
| static unsigned int |
| loongarch_build_integer (struct loongarch_integer_op *codes, |
| HOST_WIDE_INT value) |
| |
| { |
| unsigned int cost = 0; |
| |
| /* Get the lower 32 bits of the value. */ |
| HOST_WIDE_INT low_part = (int32_t)value; |
| |
| if (IMM12_OPERAND (low_part) || IMM12_OPERAND_UNSIGNED (low_part)) |
| { |
| /* The value of the lower 32 bit be loaded with one instruction. |
| lu12i.w. */ |
| codes[0].code = UNKNOWN; |
| codes[0].method = METHOD_NORMAL; |
| codes[0].value = low_part; |
| cost++; |
| } |
| else |
| { |
| /* lu12i.w + ior. */ |
| codes[0].code = UNKNOWN; |
| codes[0].method = METHOD_NORMAL; |
| codes[0].value = low_part & ~(IMM_REACH - 1); |
| cost++; |
| HOST_WIDE_INT iorv = low_part & (IMM_REACH - 1); |
| if (iorv != 0) |
| { |
| codes[1].code = IOR; |
| codes[1].method = METHOD_NORMAL; |
| codes[1].value = iorv; |
| cost++; |
| } |
| } |
| |
| if (TARGET_64BIT) |
| { |
| bool lu32i[2] = {(value & LU32I_B) == 0, (value & LU32I_B) == LU32I_B}; |
| bool lu52i[2] = {(value & LU52I_B) == 0, (value & LU52I_B) == LU52I_B}; |
| |
| int sign31 = (value & (HOST_WIDE_INT_1U << 31)) >> 31; |
| int sign51 = (value & (HOST_WIDE_INT_1U << 51)) >> 51; |
| /* Determine whether the upper 32 bits are sign-extended from the lower |
| 32 bits. If it is, the instructions to load the high order can be |
| ommitted. */ |
| if (lu32i[sign31] && lu52i[sign31]) |
| return cost; |
| /* Determine whether bits 32-51 are sign-extended from the lower 32 |
| bits. If so, directly load 52-63 bits. */ |
| else if (lu32i[sign31]) |
| { |
| codes[cost].method = METHOD_LU52I; |
| codes[cost].value = value & LU52I_B; |
| return cost + 1; |
| } |
| |
| codes[cost].method = METHOD_LU32I; |
| codes[cost].value = (value & LU32I_B) | (sign51 ? LU52I_B : 0); |
| cost++; |
| |
| /* Determine whether the 52-61 bits are sign-extended from the low order, |
| and if not, load the 52-61 bits. */ |
| if (!lu52i[(value & (HOST_WIDE_INT_1U << 51)) >> 51]) |
| { |
| codes[cost].method = METHOD_LU52I; |
| codes[cost].value = value & LU52I_B; |
| cost++; |
| } |
| } |
| |
| gcc_assert (cost <= LARCH_MAX_INTEGER_OPS); |
| |
| return cost; |
| } |
| |
| /* Fill CODES with a sequence of rtl operations to load VALUE. |
| Return the number of operations needed. |
| Split interger in loongarch_output_move. */ |
| |
| static unsigned int |
| loongarch_integer_cost (HOST_WIDE_INT value) |
| { |
| struct loongarch_integer_op codes[LARCH_MAX_INTEGER_OPS]; |
| return loongarch_build_integer (codes, value); |
| } |
| |
| /* Implement TARGET_LEGITIMATE_CONSTANT_P. */ |
| |
| static bool |
| loongarch_legitimate_constant_p (machine_mode mode ATTRIBUTE_UNUSED, rtx x) |
| { |
| return loongarch_const_insns (x) > 0; |
| } |
| |
| /* Return true if X is a thread-local symbol. */ |
| |
| static bool |
| loongarch_tls_symbol_p (rtx x) |
| { |
| return SYMBOL_REF_P (x) && SYMBOL_REF_TLS_MODEL (x) != 0; |
| } |
| |
| /* Return true if SYMBOL_REF X is associated with a global symbol |
| (in the STB_GLOBAL sense). */ |
| |
| bool |
| loongarch_global_symbol_p (const_rtx x) |
| { |
| if (LABEL_REF_P (x)) |
| return false; |
| |
| const_tree decl = SYMBOL_REF_DECL (x); |
| |
| if (!decl) |
| return !SYMBOL_REF_LOCAL_P (x) || SYMBOL_REF_EXTERNAL_P (x); |
| |
| /* Weakref symbols are not TREE_PUBLIC, but their targets are global |
| or weak symbols. Relocations in the object file will be against |
| the target symbol, so it's that symbol's binding that matters here. */ |
| return DECL_P (decl) && (TREE_PUBLIC (decl) || DECL_WEAK (decl)); |
| } |
| |
| bool |
| loongarch_global_symbol_noweak_p (const_rtx x) |
| { |
| if (LABEL_REF_P (x)) |
| return false; |
| |
| const_tree decl = SYMBOL_REF_DECL (x); |
| |
| if (!decl) |
| return !SYMBOL_REF_LOCAL_P (x) || SYMBOL_REF_EXTERNAL_P (x); |
| |
| return DECL_P (decl) && TREE_PUBLIC (decl); |
| } |
| |
| bool |
| loongarch_weak_symbol_p (const_rtx x) |
| { |
| const_tree decl; |
| if (LABEL_REF_P (x) || !(decl = SYMBOL_REF_DECL (x))) |
| return false; |
| return DECL_P (decl) && DECL_WEAK (decl); |
| } |
| |
| /* Return true if SYMBOL_REF X binds locally. */ |
| |
| bool |
| loongarch_symbol_binds_local_p (const_rtx x) |
| { |
| if (TARGET_DIRECT_EXTERN_ACCESS) |
| return true; |
| |
| 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 true if rtx constants of mode MODE should be put into a small |
| data section. */ |
| |
| static bool |
| loongarch_rtx_constant_in_small_data_p (machine_mode mode) |
| { |
| return (GET_MODE_SIZE (mode) <= g_switch_value); |
| } |
| |
| /* Return the method that should be used to access SYMBOL_REF or |
| LABEL_REF X. */ |
| |
| static enum loongarch_symbol_type |
| loongarch_classify_symbol (const_rtx x) |
| { |
| enum loongarch_symbol_type pcrel = |
| TARGET_CMODEL_EXTREME ? SYMBOL_PCREL64 : SYMBOL_PCREL; |
| |
| if (!SYMBOL_REF_P (x)) |
| return pcrel; |
| |
| if (SYMBOL_REF_TLS_MODEL (x)) |
| return SYMBOL_TLS; |
| |
| if (!loongarch_symbol_binds_local_p (x)) |
| return SYMBOL_GOT_DISP; |
| |
| tree t = SYMBOL_REF_DECL (x); |
| if (!t) |
| return pcrel; |
| |
| t = lookup_attribute ("model", DECL_ATTRIBUTES (t)); |
| if (!t) |
| return pcrel; |
| |
| t = TREE_VALUE (TREE_VALUE (t)); |
| |
| /* loongarch_handle_model_attribute should reject other values. */ |
| gcc_assert (TREE_CODE (t) == STRING_CST); |
| |
| const char *model = TREE_STRING_POINTER (t); |
| if (strcmp (model, "normal") == 0) |
| return SYMBOL_PCREL; |
| if (strcmp (model, "extreme") == 0) |
| return SYMBOL_PCREL64; |
| |
| /* loongarch_handle_model_attribute should reject unknown model |
| name. */ |
| gcc_unreachable (); |
| } |
| |
| /* Classify the base of symbolic expression X, given that X appears in |
| context CONTEXT. */ |
| |
| static enum loongarch_symbol_type |
| loongarch_classify_symbolic_expression (rtx x) |
| { |
| rtx offset; |
| |
| split_const (x, &x, &offset); |
| if (UNSPEC_ADDRESS_P (x)) |
| return UNSPEC_ADDRESS_TYPE (x); |
| |
| return loongarch_classify_symbol (x); |
| } |
| |
| /* Return true if X is a symbolic constant. If it is, |
| store the type of the symbol in *SYMBOL_TYPE. */ |
| |
| bool |
| loongarch_symbolic_constant_p (rtx x, enum loongarch_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 (SYMBOL_REF_P (x) || LABEL_REF_P (x)) |
| { |
| *symbol_type = loongarch_classify_symbol (x); |
| if (*symbol_type == SYMBOL_TLS) |
| return true; |
| } |
| else |
| return false; |
| |
| if (offset == const0_rtx) |
| return true; |
| |
| /* Check whether a nonzero offset is valid for the underlying |
| relocations. */ |
| switch (*symbol_type) |
| { |
| case SYMBOL_TLS_IE: |
| case SYMBOL_TLS_LE: |
| case SYMBOL_TLSGD: |
| case SYMBOL_TLSLDM: |
| case SYMBOL_PCREL: |
| case SYMBOL_PCREL64: |
| /* GAS rejects offsets outside the range [-2^31, 2^31-1]. */ |
| return sext_hwi (INTVAL (offset), 32) == INTVAL (offset); |
| |
| case SYMBOL_GOT_DISP: |
| case SYMBOL_TLS: |
| return false; |
| } |
| gcc_unreachable (); |
| } |
| |
| /* Returns the number of instructions necessary to reference a symbol. */ |
| |
| static int |
| loongarch_symbol_insns (enum loongarch_symbol_type type, machine_mode mode) |
| { |
| switch (type) |
| { |
| case SYMBOL_GOT_DISP: |
| /* The constant will have to be loaded from the GOT before it |
| is used in an address. */ |
| if (!TARGET_EXPLICIT_RELOCS && mode != MAX_MACHINE_MODE) |
| return 0; |
| |
| return 3; |
| |
| case SYMBOL_PCREL: |
| case SYMBOL_TLS_IE: |
| case SYMBOL_TLS_LE: |
| return 2; |
| |
| case SYMBOL_TLSGD: |
| case SYMBOL_TLSLDM: |
| return 3; |
| |
| case SYMBOL_PCREL64: |
| return 5; |
| |
| case SYMBOL_TLS: |
| /* We don't treat a bare TLS symbol as a constant. */ |
| return 0; |
| } |
| gcc_unreachable (); |
| } |
| |
| /* Implement TARGET_CANNOT_FORCE_CONST_MEM. */ |
| |
| static bool |
| loongarch_cannot_force_const_mem (machine_mode mode, rtx x) |
| { |
| enum loongarch_symbol_type type; |
| rtx base, offset; |
| |
| /* As an optimization, reject constants that loongarch_legitimize_move |
| can expand inline. |
| |
| Suppose we have a multi-instruction sequence that loads constant C |
| into register R. If R does not get allocated a hard register, and |
| R is used in an operand that allows both registers and memory |
| references, reload will consider forcing C into memory and using |
| one of the instruction's memory alternatives. Returning false |
| here will force it to use an input reload instead. */ |
| if (CONST_INT_P (x) && loongarch_legitimate_constant_p (mode, x)) |
| return true; |
| |
| split_const (x, &base, &offset); |
| if (loongarch_symbolic_constant_p (base, &type)) |
| { |
| /* The same optimization as for CONST_INT. */ |
| if (IMM12_INT (offset) |
| && loongarch_symbol_insns (type, MAX_MACHINE_MODE) > 0) |
| return true; |
| } |
| |
| /* TLS symbols must be computed by loongarch_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 |
| loongarch_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 |
| loongarch_valid_base_register_p (rtx x, machine_mode mode, bool strict_p) |
| { |
| if (!strict_p && SUBREG_P (x)) |
| x = SUBREG_REG (x); |
| |
| return (REG_P (x) |
| && loongarch_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 |
| loongarch_valid_offset_p (rtx x, machine_mode mode) |
| { |
| /* Check that X is a signed 12-bit number, |
| or check that X is a signed 16-bit number |
| and offset 4 byte aligned. */ |
| if (!(const_arith_operand (x, Pmode) |
| || ((mode == E_SImode || mode == E_DImode) |
| && const_imm16_operand (x, Pmode) |
| && (loongarch_signed_immediate_p (INTVAL (x), 14, 2))))) |
| 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 |
| && !IMM12_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 or more? */ |
| |
| bool |
| loongarch_split_symbol_type (enum loongarch_symbol_type symbol_type) |
| { |
| switch (symbol_type) |
| { |
| case SYMBOL_PCREL: |
| case SYMBOL_PCREL64: |
| case SYMBOL_GOT_DISP: |
| case SYMBOL_TLS_IE: |
| case SYMBOL_TLS_LE: |
| case SYMBOL_TLSGD: |
| case SYMBOL_TLSLDM: |
| return true; |
| |
| case SYMBOL_TLS: |
| return false; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Return true if a LO_SUM can address a value of mode MODE when the |
| LO_SUM symbol has type SYMBOL_TYPE. */ |
| |
| static bool |
| loongarch_valid_lo_sum_p (enum loongarch_symbol_type symbol_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 (loongarch_symbol_insns (symbol_type, mode) == 0) |
| return false; |
| |
| /* Check that there is a known low-part relocation. */ |
| if (!loongarch_split_symbol_type (symbol_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; |
| } |
| |
| static bool |
| loongarch_valid_index_p (struct loongarch_address_info *info, rtx x, |
| machine_mode mode, bool strict_p) |
| { |
| rtx index; |
| |
| if ((REG_P (x) || SUBREG_P (x)) |
| && GET_MODE (x) == Pmode) |
| { |
| index = x; |
| } |
| else |
| return false; |
| |
| if (!strict_p |
| && SUBREG_P (index) |
| && contains_reg_of_mode[GENERAL_REGS][GET_MODE (SUBREG_REG (index))]) |
| index = SUBREG_REG (index); |
| |
| if (loongarch_valid_base_register_p (index, mode, strict_p)) |
| { |
| info->type = ADDRESS_REG_REG; |
| info->offset = index; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* 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 |
| loongarch_classify_address (struct loongarch_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 loongarch_valid_base_register_p (info->reg, mode, strict_p); |
| |
| case PLUS: |
| if (loongarch_valid_base_register_p (XEXP (x, 0), mode, strict_p) |
| && loongarch_valid_index_p (info, XEXP (x, 1), mode, strict_p)) |
| { |
| info->reg = XEXP (x, 0); |
| return true; |
| } |
| |
| if (loongarch_valid_base_register_p (XEXP (x, 1), mode, strict_p) |
| && loongarch_valid_index_p (info, XEXP (x, 0), mode, strict_p)) |
| { |
| info->reg = XEXP (x, 1); |
| return true; |
| } |
| |
| info->type = ADDRESS_REG; |
| info->reg = XEXP (x, 0); |
| info->offset = XEXP (x, 1); |
| return (loongarch_valid_base_register_p (info->reg, mode, strict_p) |
| && loongarch_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 MIPS |
| 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 |
| = loongarch_classify_symbolic_expression (info->offset); |
| return (loongarch_valid_base_register_p (info->reg, mode, strict_p) |
| && loongarch_valid_lo_sum_p (info->symbol_type, mode, |
| info->offset)); |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* Implement TARGET_LEGITIMATE_ADDRESS_P. */ |
| |
| static bool |
| loongarch_legitimate_address_p (machine_mode mode, rtx x, bool strict_p) |
| { |
| struct loongarch_address_info addr; |
| |
| return loongarch_classify_address (&addr, x, mode, strict_p); |
| } |
| |
| /* Return true if ADDR matches the pattern for the indexed address |
| instruction. */ |
| |
| static bool |
| loongarch_index_address_p (rtx addr, machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| if (GET_CODE (addr) != PLUS |
| || !REG_P (XEXP (addr, 0)) |
| || !REG_P (XEXP (addr, 1))) |
| return false; |
| return true; |
| } |
| |
| /* 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 |
| loongarch_address_insns (rtx x, machine_mode mode, bool might_split_p) |
| { |
| struct loongarch_address_info addr; |
| int factor; |
| |
| if (!loongarch_classify_address (&addr, x, mode, false)) |
| return 0; |
| |
| /* BLKmode is used for single unaligned loads and stores and should |
| not count as a multiword mode. (GET_MODE_SIZE (BLKmode) is pretty |
| meaningless, so we have to single it out as a special case one way |
| or the other.) */ |
| if (mode != BLKmode && might_split_p) |
| factor = (GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD; |
| else |
| factor = 1; |
| |
| if (loongarch_classify_address (&addr, x, mode, false)) |
| switch (addr.type) |
| { |
| case ADDRESS_REG: |
| case ADDRESS_REG_REG: |
| case ADDRESS_CONST_INT: |
| return factor; |
| |
| case ADDRESS_LO_SUM: |
| return factor + 1; |
| |
| case ADDRESS_SYMBOLIC: |
| return factor * loongarch_symbol_insns (addr.symbol_type, mode); |
| } |
| return 0; |
| } |
| |
| /* Return true if X fits within an unsigned field of BITS bits that is |
| shifted left SHIFT bits before being used. */ |
| |
| bool |
| loongarch_unsigned_immediate_p (unsigned HOST_WIDE_INT x, int bits, |
| int shift = 0) |
| { |
| return (x & ((1 << shift) - 1)) == 0 && x < ((unsigned) 1 << (shift + bits)); |
| } |
| |
| /* Return true if X fits within a signed field of BITS bits that is |
| shifted left SHIFT bits before being used. */ |
| |
| bool |
| loongarch_signed_immediate_p (unsigned HOST_WIDE_INT x, int bits, |
| int shift = 0) |
| { |
| x += 1 << (bits + shift - 1); |
| return loongarch_unsigned_immediate_p (x, bits, shift); |
| } |
| |
| /* Return true if X is a legitimate address with a 12-bit offset |
| or addr.type is ADDRESS_LO_SUM. |
| MODE is the mode of the value being accessed. */ |
| |
| bool |
| loongarch_12bit_offset_address_p (rtx x, machine_mode mode) |
| { |
| struct loongarch_address_info addr; |
| |
| return (loongarch_classify_address (&addr, x, mode, false) |
| && ((addr.type == ADDRESS_REG |
| && CONST_INT_P (addr.offset) |
| && LARCH_12BIT_OFFSET_P (INTVAL (addr.offset))) |
| || addr.type == ADDRESS_LO_SUM)); |
| } |
| |
| /* Return true if X is a legitimate address with a 14-bit offset shifted 2. |
| MODE is the mode of the value being accessed. */ |
| |
| bool |
| loongarch_14bit_shifted_offset_address_p (rtx x, machine_mode mode) |
| { |
| struct loongarch_address_info addr; |
| |
| return (loongarch_classify_address (&addr, x, mode, false) |
| && addr.type == ADDRESS_REG |
| && CONST_INT_P (addr.offset) |
| && LARCH_16BIT_OFFSET_P (INTVAL (addr.offset)) |
| && LARCH_SHIFT_2_OFFSET_P (INTVAL (addr.offset))); |
| } |
| |
| /* Return true if X is a legitimate address with base and index. |
| MODE is the mode of the value being accessed. */ |
| |
| bool |
| loongarch_base_index_address_p (rtx x, machine_mode mode) |
| { |
| struct loongarch_address_info addr; |
| |
| return (loongarch_classify_address (&addr, x, mode, false) |
| && addr.type == ADDRESS_REG_REG |
| && REG_P (addr.offset)); |
| } |
| |
| /* Return the number of instructions needed to load constant X, |
| Return 0 if X isn't a valid constant. */ |
| |
| int |
| loongarch_const_insns (rtx x) |
| { |
| enum loongarch_symbol_type symbol_type; |
| rtx offset; |
| |
| switch (GET_CODE (x)) |
| { |
| case HIGH: |
| if (!loongarch_symbolic_constant_p (XEXP (x, 0), &symbol_type) |
| || !loongarch_split_symbol_type (symbol_type)) |
| return 0; |
| |
| /* This is simply a PCALAU12I. */ |
| return 1; |
| |
| case CONST_INT: |
| return loongarch_integer_cost (INTVAL (x)); |
| |
| case CONST_VECTOR: |
| /* Fall through. */ |
| case CONST_DOUBLE: |
| return x == CONST0_RTX (GET_MODE (x)) ? 1 : 0; |
| |
| case CONST: |
| /* See if we can refer to X directly. */ |
| if (loongarch_symbolic_constant_p (x, &symbol_type)) |
| return loongarch_symbol_insns (symbol_type, MAX_MACHINE_MODE); |
| |
| /* Otherwise try splitting the constant into a base and offset. |
| If the offset is a 12-bit value, we can load the base address |
| into a register and then use ADDI.{W/D} to add in the offset. |
| If the offset is larger, we can load the base and offset |
| into separate registers and add them together with ADD.{W/D}. |
| However, the latter is only possible before reload; during |
| and after reload, we must have the option of forcing the |
| constant into the pool instead. */ |
| split_const (x, &x, &offset); |
| if (offset != 0) |
| { |
| int n = loongarch_const_insns (x); |
| if (n != 0) |
| { |
| if (IMM12_INT (offset)) |
| return n + 1; |
| else if (!targetm.cannot_force_const_mem (GET_MODE (x), x)) |
| return n + 1 + loongarch_integer_cost (INTVAL (offset)); |
| } |
| } |
| return 0; |
| |
| case SYMBOL_REF: |
| case LABEL_REF: |
| return loongarch_symbol_insns ( |
| loongarch_classify_symbol (x), MAX_MACHINE_MODE); |
| |
| 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 |
| loongarch_split_const_insns (rtx x) |
| { |
| unsigned int low, high; |
| |
| low = loongarch_const_insns (loongarch_subword (x, false)); |
| high = loongarch_const_insns (loongarch_subword (x, true)); |
| gcc_assert (low > 0 && high > 0); |
| return low + high; |
| } |
| |
| static bool loongarch_split_move_insn_p (rtx dest, rtx src); |
| |
| /* Return the number of instructions needed to implement INSN, |
| given that it loads from or stores to MEM. */ |
| |
| int |
| loongarch_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 = GET_MODE_SIZE (mode) > UNITS_PER_WORD; |
| if (might_split_p) |
| { |
| set = single_set (insn); |
| if (set |
| && !loongarch_split_move_insn_p (SET_DEST (set), SET_SRC (set))) |
| might_split_p = false; |
| } |
| |
| return loongarch_address_insns (XEXP (mem, 0), mode, might_split_p); |
| } |
| |
| /* Return true if we need to trap on division by zero. */ |
| |
| bool |
| loongarch_check_zero_div_p (void) |
| { |
| /* if -m[no-]check-zero-division is given explicitly. */ |
| if (target_flags_explicit & MASK_CHECK_ZERO_DIV) |
| return TARGET_CHECK_ZERO_DIV; |
| |
| /* if not, don't trap for optimized code except -Og. */ |
| return !optimize || optimize_debug; |
| } |
| |
| /* Return the number of instructions needed for an integer division. */ |
| |
| int |
| loongarch_idiv_insns (machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| int count; |
| |
| count = 1; |
| if (loongarch_check_zero_div_p ()) |
| count += 2; |
| |
| return count; |
| } |
| |
| /* Emit an instruction of the form (set TARGET (CODE OP0 OP1)). */ |
| |
| void |
| loongarch_emit_binary (enum rtx_code code, rtx target, rtx op0, rtx op1) |
| { |
| emit_insn (gen_rtx_SET (target, gen_rtx_fmt_ee (code, GET_MODE (target), |
| op0, op1))); |
| } |
| |
| /* Compute (CODE OP0 OP1) and store the result in a new register |
| of mode MODE. Return that new register. */ |
| |
| static rtx |
| loongarch_force_binary (machine_mode mode, enum rtx_code code, rtx op0, |
| rtx op1) |
| { |
| rtx reg; |
| |
| reg = gen_reg_rtx (mode); |
| loongarch_emit_binary (code, reg, op0, op1); |
| return reg; |
| } |
| |
| /* 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 |
| loongarch_force_temporary (rtx dest, rtx value) |
| { |
| if (can_create_pseudo_p ()) |
| return force_reg (Pmode, value); |
| else |
| { |
| loongarch_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 |
| loongarch_unspec_address_offset (rtx base, rtx offset, |
| enum loongarch_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 |
| loongarch_unspec_address (rtx address, enum loongarch_symbol_type symbol_type) |
| { |
| rtx base, offset; |
| |
| split_const (address, &base, &offset); |
| return loongarch_unspec_address_offset (base, offset, symbol_type); |
| } |
| |
| /* Emit an instruction of the form (set TARGET SRC). */ |
| |
| static rtx |
| loongarch_emit_set (rtx target, rtx src) |
| { |
| emit_insn (gen_rtx_SET (target, src)); |
| return target; |
| } |
| |
| /* If OP is an UNSPEC address, return the address to which it refers, |
| otherwise return OP itself. */ |
| |
| rtx |
| loongarch_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; |
| } |
| |
| /* Return a legitimate address for REG + OFFSET. TEMP is as for |
| loongarch_force_temporary; it is only needed when OFFSET is not a |
| IMM12_OPERAND. */ |
| |
| static rtx |
| loongarch_add_offset (rtx temp, rtx reg, HOST_WIDE_INT offset) |
| { |
| if (!IMM12_OPERAND (offset)) |
| { |
| rtx high; |
| |
| /* Leave OFFSET as a 12-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 = loongarch_force_temporary (temp, high); |
| reg = loongarch_force_temporary (temp, gen_rtx_PLUS (Pmode, high, reg)); |
| } |
| return plus_constant (Pmode, reg, offset); |
| } |
| |
| /* The __tls_get_attr symbol. */ |
| static GTY (()) rtx loongarch_tls_symbol; |
| |
| /* Load an entry from the GOT for a TLS GD access. */ |
| |
| static rtx |
| loongarch_got_load_tls_gd (rtx dest, rtx sym) |
| { |
| return gen_got_load_tls_gd (Pmode, dest, sym); |
| } |
| |
| /* Load an entry from the GOT for a TLS LD access. */ |
| |
| static rtx |
| loongarch_got_load_tls_ld (rtx dest, rtx sym) |
| { |
| return gen_got_load_tls_ld (Pmode, dest, sym); |
| } |
| |
| /* Load an entry from the GOT for a TLS IE access. */ |
| |
| static rtx |
| loongarch_got_load_tls_ie (rtx dest, rtx sym) |
| { |
| return gen_got_load_tls_ie (Pmode, dest, sym); |
| } |
| |
| /* Add in the thread pointer for a TLS LE access. */ |
| |
| static rtx |
| loongarch_got_load_tls_le (rtx dest, rtx sym) |
| { |
| return gen_got_load_tls_le (Pmode, dest, sym); |
| } |
| |
| /* 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). V0 is an RTX for the |
| return value location. */ |
| |
| static rtx_insn * |
| loongarch_call_tls_get_addr (rtx sym, enum loongarch_symbol_type type, rtx v0) |
| { |
| rtx loc, a0; |
| rtx_insn *insn; |
| rtx tmp = gen_reg_rtx (Pmode); |
| |
| a0 = gen_rtx_REG (Pmode, GP_ARG_FIRST); |
| |
| if (!loongarch_tls_symbol) |
| loongarch_tls_symbol = init_one_libfunc ("__tls_get_addr"); |
| |
| loc = loongarch_unspec_address (sym, type); |
| |
| start_sequence (); |
| |
| if (TARGET_EXPLICIT_RELOCS) |
| { |
| /* Split tls symbol to high and low. */ |
| rtx high = gen_rtx_HIGH (Pmode, copy_rtx (loc)); |
| high = loongarch_force_temporary (tmp, high); |
| |
| if (TARGET_CMODEL_EXTREME) |
| { |
| gcc_assert (TARGET_EXPLICIT_RELOCS); |
| |
| rtx tmp1 = gen_reg_rtx (Pmode); |
| emit_insn (gen_tls_low (Pmode, tmp1, gen_rtx_REG (Pmode, 0), loc)); |
| emit_insn (gen_lui_h_lo20 (tmp1, tmp1, loc)); |
| emit_insn (gen_lui_h_hi12 (tmp1, tmp1, loc)); |
| emit_move_insn (a0, gen_rtx_PLUS (Pmode, high, tmp1)); |
| } |
| else |
| emit_insn (gen_tls_low (Pmode, a0, high, loc)); |
| } |
| else |
| { |
| if (type == SYMBOL_TLSLDM) |
| emit_insn (loongarch_got_load_tls_ld (a0, loc)); |
| else if (type == SYMBOL_TLSGD) |
| emit_insn (loongarch_got_load_tls_gd (a0, loc)); |
| else |
| gcc_unreachable (); |
| } |
| |
| if (flag_plt) |
| { |
| switch (la_opt_cmodel) |
| { |
| case CMODEL_NORMAL: |
| insn = emit_call_insn (gen_call_value_internal (v0, |
| loongarch_tls_symbol, |
| const0_rtx)); |
| break; |
| |
| case CMODEL_MEDIUM: |
| { |
| rtx reg = gen_reg_rtx (Pmode); |
| if (TARGET_EXPLICIT_RELOCS) |
| { |
| emit_insn (gen_pcalau12i (Pmode, reg, loongarch_tls_symbol)); |
| rtx call = gen_call_value_internal_1 (Pmode, v0, reg, |
| loongarch_tls_symbol, |
| const0_rtx); |
| insn = emit_call_insn (call); |
| } |
| else |
| { |
| emit_move_insn (reg, loongarch_tls_symbol); |
| insn = emit_call_insn (gen_call_value_internal (v0, |
| reg, |
| const0_rtx)); |
| } |
| break; |
| } |
| |
| /* code model extreme not support plt. */ |
| case CMODEL_EXTREME: |
| case CMODEL_LARGE: |
| case CMODEL_TINY: |
| case CMODEL_TINY_STATIC: |
| default: |
| gcc_unreachable (); |
| } |
| } |
| else |
| { |
| rtx dest = gen_reg_rtx (Pmode); |
| |
| switch (la_opt_cmodel) |
| { |
| case CMODEL_NORMAL: |
| case CMODEL_MEDIUM: |
| { |
| if (TARGET_EXPLICIT_RELOCS) |
| { |
| rtx high = gen_reg_rtx (Pmode); |
| loongarch_emit_move (high, |
| gen_rtx_HIGH (Pmode, |
| loongarch_tls_symbol)); |
| emit_insn (gen_ld_from_got (Pmode, dest, high, |
| loongarch_tls_symbol)); |
| } |
| else |
| loongarch_emit_move (dest, loongarch_tls_symbol); |
| break; |
| } |
| |
| case CMODEL_EXTREME: |
| { |
| gcc_assert (TARGET_EXPLICIT_RELOCS); |
| |
| rtx tmp1 = gen_reg_rtx (Pmode); |
| rtx high = gen_reg_rtx (Pmode); |
| |
| loongarch_emit_move (high, |
| gen_rtx_HIGH (Pmode, loongarch_tls_symbol)); |
| loongarch_emit_move (tmp1, gen_rtx_LO_SUM (Pmode, |
| gen_rtx_REG (Pmode, 0), |
| loongarch_tls_symbol)); |
| emit_insn (gen_lui_h_lo20 (tmp1, tmp1, loongarch_tls_symbol)); |
| emit_insn (gen_lui_h_hi12 (tmp1, tmp1, loongarch_tls_symbol)); |
| loongarch_emit_move (dest, |
| gen_rtx_MEM (Pmode, |
| gen_rtx_PLUS (Pmode, |
| high, tmp1))); |
| } |
| break; |
| |
| case CMODEL_LARGE: |
| case CMODEL_TINY: |
| case CMODEL_TINY_STATIC: |
| default: |
| gcc_unreachable (); |
| } |
| |
| insn = emit_call_insn (gen_call_value_internal (v0, dest, const0_rtx)); |
| } |
| |
| 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 |
| loongarch_legitimize_tls_address (rtx loc) |
| { |
| rtx dest, tp, tmp, tmp1, tmp2, tmp3; |
| enum tls_model model = SYMBOL_REF_TLS_MODEL (loc); |
| rtx_insn *insn; |
| |
| switch (model) |
| { |
| case TLS_MODEL_LOCAL_DYNAMIC: |
| tmp = gen_rtx_REG (Pmode, GP_RETURN); |
| dest = gen_reg_rtx (Pmode); |
| insn = loongarch_call_tls_get_addr (loc, SYMBOL_TLSLDM, tmp); |
| emit_libcall_block (insn, dest, tmp, loc); |
| break; |
| |
| case TLS_MODEL_GLOBAL_DYNAMIC: |
| tmp = gen_rtx_REG (Pmode, GP_RETURN); |
| dest = gen_reg_rtx (Pmode); |
| insn = loongarch_call_tls_get_addr (loc, SYMBOL_TLSGD, tmp); |
| emit_libcall_block (insn, dest, tmp, loc); |
| break; |
| |
| case TLS_MODEL_INITIAL_EXEC: |
| { |
| /* la.tls.ie; tp-relative add. */ |
| tp = gen_rtx_REG (Pmode, THREAD_POINTER_REGNUM); |
| tmp1 = gen_reg_rtx (Pmode); |
| dest = gen_reg_rtx (Pmode); |
| if (TARGET_EXPLICIT_RELOCS) |
| { |
| tmp2 = loongarch_unspec_address (loc, SYMBOL_TLS_IE); |
| tmp3 = gen_reg_rtx (Pmode); |
| rtx high = gen_rtx_HIGH (Pmode, copy_rtx (tmp2)); |
| high = loongarch_force_temporary (tmp3, high); |
| |
| if (TARGET_CMODEL_EXTREME) |
| { |
| gcc_assert (TARGET_EXPLICIT_RELOCS); |
| |
| rtx tmp3 = gen_reg_rtx (Pmode); |
| emit_insn (gen_tls_low (Pmode, tmp3, |
| gen_rtx_REG (Pmode, 0), tmp2)); |
| emit_insn (gen_lui_h_lo20 (tmp3, tmp3, tmp2)); |
| emit_insn (gen_lui_h_hi12 (tmp3, tmp3, tmp2)); |
| emit_move_insn (tmp1, |
| gen_rtx_MEM (Pmode, |
| gen_rtx_PLUS (Pmode, |
| high, tmp3))); |
| } |
| else |
| emit_insn (gen_ld_from_got (Pmode, tmp1, high, tmp2)); |
| } |
| else |
| emit_insn (loongarch_got_load_tls_ie (tmp1, loc)); |
| emit_insn (gen_add3_insn (dest, tmp1, tp)); |
| } |
| break; |
| |
| case TLS_MODEL_LOCAL_EXEC: |
| { |
| /* la.tls.le; tp-relative add. */ |
| tp = gen_rtx_REG (Pmode, THREAD_POINTER_REGNUM); |
| tmp1 = gen_reg_rtx (Pmode); |
| dest = gen_reg_rtx (Pmode); |
| |
| if (TARGET_EXPLICIT_RELOCS) |
| { |
| tmp2 = loongarch_unspec_address (loc, SYMBOL_TLS_LE); |
| tmp3 = gen_reg_rtx (Pmode); |
| rtx high = gen_rtx_HIGH (Pmode, copy_rtx (tmp2)); |
| high = loongarch_force_temporary (tmp3, high); |
| emit_insn (gen_ori_l_lo12 (Pmode, tmp1, high, tmp2)); |
| |
| if (TARGET_CMODEL_EXTREME) |
| { |
| gcc_assert (TARGET_EXPLICIT_RELOCS); |
| |
| emit_insn (gen_lui_h_lo20 (tmp1, tmp1, tmp2)); |
| emit_insn (gen_lui_h_hi12 (tmp1, tmp1, tmp2)); |
| } |
| } |
| else |
| emit_insn (loongarch_got_load_tls_le (tmp1, loc)); |
| emit_insn (gen_add3_insn (dest, tmp1, tp)); |
| } |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| return dest; |
| } |
| |
| rtx |
| loongarch_legitimize_call_address (rtx addr) |
| { |
| if (!call_insn_operand (addr, VOIDmode)) |
| { |
| rtx reg = gen_reg_rtx (Pmode); |
| loongarch_emit_move (reg, addr); |
| return reg; |
| } |
| |
| enum loongarch_symbol_type symbol_type = loongarch_classify_symbol (addr); |
| |
| /* Split function call insn 'bl sym' or 'bl %plt(sym)' to : |
| pcalau12i $rd, %pc_hi20(sym) |
| jr $rd, %pc_lo12(sym). */ |
| |
| if (TARGET_CMODEL_MEDIUM |
| && TARGET_EXPLICIT_RELOCS |
| && (SYMBOL_REF_P (addr) || LABEL_REF_P (addr)) |
| && (symbol_type == SYMBOL_PCREL |
| || (symbol_type == SYMBOL_GOT_DISP && flag_plt))) |
| { |
| rtx reg = gen_reg_rtx (Pmode); |
| emit_insn (gen_pcalau12i (Pmode, reg, addr)); |
| return gen_rtx_LO_SUM (Pmode, reg, addr); |
| } |
| |
| return addr; |
| } |
| |
| /* If X is a PLUS of a CONST_INT, return the two terms in *BASE_PTR |
| and *OFFSET_PTR. Return X in *BASE_PTR and 0 in *OFFSET_PTR otherwise. */ |
| |
| static void |
| loongarch_split_plus (rtx x, rtx *base_ptr, HOST_WIDE_INT *offset_ptr) |
| { |
| if (GET_CODE (x) == PLUS && CONST_INT_P (XEXP (x, 1))) |
| { |
| *base_ptr = XEXP (x, 0); |
| *offset_ptr = INTVAL (XEXP (x, 1)); |
| } |
| else |
| { |
| *base_ptr = x; |
| *offset_ptr = 0; |
| } |
| } |
| |
| /* If X is not a valid address for mode MODE, force it into a register. */ |
| |
| static rtx |
| loongarch_force_address (rtx x, machine_mode mode) |
| { |
| if (!loongarch_legitimate_address_p (mode, x, false)) |
| x = force_reg (Pmode, x); |
| return x; |
| } |
| |
| static bool |
| loongarch_symbol_extreme_p (enum loongarch_symbol_type type) |
| { |
| switch (type) |
| { |
| case SYMBOL_PCREL: |
| return false; |
| case SYMBOL_PCREL64: |
| return true; |
| default: |
| return TARGET_CMODEL_EXTREME; |
| } |
| } |
| |
| /* 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. |
| |
| Return false if build with '-mno-explicit-relocs'. |
| |
| TEMP is as for loongarch_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 |
| loongarch_split_symbol (rtx temp, rtx addr, machine_mode mode, rtx *low_out) |
| { |
| enum loongarch_symbol_type symbol_type; |
| |
| /* If build with '-mno-explicit-relocs', don't split symbol. */ |
| if (!TARGET_EXPLICIT_RELOCS) |
| return false; |
| |
| if ((GET_CODE (addr) == HIGH && mode == MAX_MACHINE_MODE) |
| || !loongarch_symbolic_constant_p (addr, &symbol_type) |
| || loongarch_symbol_insns (symbol_type, mode) == 0 |
| || !loongarch_split_symbol_type (symbol_type)) |
| return false; |
| |
| rtx high, temp1 = NULL; |
| |
| if (temp == NULL) |
| temp = gen_reg_rtx (Pmode); |
| |
| /* Get the 12-31 bits of the address. */ |
| high = gen_rtx_HIGH (Pmode, copy_rtx (addr)); |
| high = loongarch_force_temporary (temp, high); |
| |
| if (loongarch_symbol_extreme_p (symbol_type) && can_create_pseudo_p ()) |
| { |
| gcc_assert (TARGET_EXPLICIT_RELOCS); |
| |
| temp1 = gen_reg_rtx (Pmode); |
| emit_move_insn (temp1, gen_rtx_LO_SUM (Pmode, gen_rtx_REG (Pmode, 0), |
| addr)); |
| emit_insn (gen_lui_h_lo20 (temp1, temp1, addr)); |
| emit_insn (gen_lui_h_hi12 (temp1, temp1, addr)); |
| } |
| |
| if (low_out) |
| switch (symbol_type) |
| { |
| case SYMBOL_PCREL64: |
| if (can_create_pseudo_p ()) |
| { |
| *low_out = gen_rtx_PLUS (Pmode, high, temp1); |
| break; |
| } |
| /* fall through */ |
| case SYMBOL_PCREL: |
| *low_out = gen_rtx_LO_SUM (Pmode, high, addr); |
| break; |
| |
| case SYMBOL_GOT_DISP: |
| /* SYMBOL_GOT_DISP symbols are loaded from the GOT. */ |
| { |
| if (TARGET_CMODEL_EXTREME && can_create_pseudo_p ()) |
| *low_out = gen_rtx_MEM (Pmode, gen_rtx_PLUS (Pmode, high, temp1)); |
| else |
| { |
| rtx low = gen_rtx_LO_SUM (Pmode, high, addr); |
| rtx mem = gen_rtx_MEM (Pmode, low); |
| *low_out = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, mem), |
| UNSPEC_LOAD_FROM_GOT); |
| } |
| |
| break; |
| } |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| return true; |
| } |
| |
| /* 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 |
| loongarch_legitimize_address (rtx x, rtx oldx ATTRIBUTE_UNUSED, |
| machine_mode mode) |
| { |
| rtx base, addr; |
| HOST_WIDE_INT offset; |
| |
| if (loongarch_tls_symbol_p (x)) |
| return loongarch_legitimize_tls_address (x); |
| |
| /* See if the address can split into a high part and a LO_SUM. */ |
| if (loongarch_split_symbol (NULL, x, mode, &addr)) |
| return loongarch_force_address (addr, mode); |
| |
| /* Handle BASE + OFFSET using loongarch_add_offset. */ |
| loongarch_split_plus (x, &base, &offset); |
| if (offset != 0) |
| { |
| if (!loongarch_valid_base_register_p (base, mode, false)) |
| base = copy_to_mode_reg (Pmode, base); |
| addr = loongarch_add_offset (NULL, base, offset); |
| return loongarch_force_address (addr, mode); |
| } |
| |
| return x; |
| } |
| |
| /* Load VALUE into DEST. TEMP is as for loongarch_force_temporary. */ |
| |
| void |
| loongarch_move_integer (rtx temp, rtx dest, unsigned HOST_WIDE_INT value) |
| { |
| struct loongarch_integer_op codes[LARCH_MAX_INTEGER_OPS]; |
| machine_mode mode; |
| unsigned int i, num_ops; |
| rtx x; |
| |
| mode = GET_MODE (dest); |
| num_ops = loongarch_build_integer (codes, value); |
| |
| /* Apply each binary operation to X. Invariant: X is a legitimate |
| source operand for a SET pattern. */ |
| x = GEN_INT (codes[0].value); |
| for (i = 1; i < num_ops; i++) |
| { |
| if (!can_create_pseudo_p ()) |
| { |
| emit_insn (gen_rtx_SET (temp, x)); |
| x = temp; |
| } |
| else |
| x = force_reg (mode, x); |
| |
| switch (codes[i].method) |
| { |
| case METHOD_NORMAL: |
| x = gen_rtx_fmt_ee (codes[i].code, mode, x, |
| GEN_INT (codes[i].value)); |
| break; |
| case METHOD_LU32I: |
| emit_insn ( |
| gen_rtx_SET (x, |
| gen_rtx_IOR (DImode, |
| gen_rtx_ZERO_EXTEND ( |
| DImode, gen_rtx_SUBREG (SImode, x, 0)), |
| GEN_INT (codes[i].value)))); |
| break; |
| case METHOD_LU52I: |
| emit_insn (gen_lu52i_d (x, x, GEN_INT (0xfffffffffffff), |
| GEN_INT (codes[i].value))); |
| break; |
| case METHOD_INSV: |
| emit_insn ( |
| gen_rtx_SET (gen_rtx_ZERO_EXTRACT (DImode, x, GEN_INT (20), |
| GEN_INT (32)), |
| gen_rtx_REG (DImode, 0))); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| emit_insn (gen_rtx_SET (dest, x)); |
| } |
| |
| /* Subroutine of loongarch_legitimize_move. Move constant SRC into register |
| DEST given that SRC satisfies immediate_operand but doesn't satisfy |
| move_operand. */ |
| |
| static void |
| loongarch_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)) |
| { |
| loongarch_move_integer (dest, dest, INTVAL (src)); |
| return; |
| } |
| |
| /* Split moves of symbolic constants into high and low. */ |
| if (loongarch_split_symbol (dest, src, MAX_MACHINE_MODE, &src)) |
| { |
| loongarch_emit_set (dest, src); |
| return; |
| } |
| |
| /* Generate the appropriate access sequences for TLS symbols. */ |
| if (loongarch_tls_symbol_p (src)) |
| { |
| loongarch_emit_move (dest, loongarch_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. |
| 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 = loongarch_force_temporary (dest, base); |
| loongarch_emit_move (dest, |
| loongarch_add_offset (NULL, base, INTVAL (offset))); |
| return; |
| } |
| |
| src = force_const_mem (mode, src); |
| |
| loongarch_emit_move (dest, src); |
| } |
| |
| /* If (set DEST SRC) is not a valid move instruction, emit an equivalent |
| sequence that is valid. */ |
| |
| bool |
| loongarch_legitimize_move (machine_mode mode, rtx dest, rtx src) |
| { |
| if (!register_operand (dest, mode) && !reg_or_0_operand (src, mode)) |
| { |
| loongarch_emit_move (dest, force_reg (mode, src)); |
| return true; |
| } |
| |
| /* Both src and dest are non-registers; one special case is supported where |
| the source is (const_int 0) and the store can source the zero register. |
| */ |
| if (!register_operand (dest, mode) && !register_operand (src, mode) |
| && !const_0_operand (src, mode)) |
| { |
| loongarch_emit_move (dest, force_reg (mode, src)); |
| 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)) |
| { |
| loongarch_legitimize_const_move (mode, dest, src); |
| set_unique_reg_note (get_last_insn (), REG_EQUAL, copy_rtx (src)); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Return true if OP refers to small data symbols directly. */ |
| |
| static int |
| loongarch_small_data_pattern_1 (rtx x) |
| { |
| subrtx_var_iterator::array_type array; |
| FOR_EACH_SUBRTX_VAR (iter, array, x, ALL) |
| { |
| rtx x = *iter; |
| |
| /* We make no particular guarantee about which symbolic constants are |
| acceptable as asm operands versus which must be forced into a GPR. */ |
| if (GET_CODE (x) == ASM_OPERANDS) |
| iter.skip_subrtxes (); |
| else if (MEM_P (x)) |
| { |
| if (loongarch_small_data_pattern_1 (XEXP (x, 0))) |
| return true; |
| iter.skip_subrtxes (); |
| } |
| } |
| return false; |
| } |
| |
| /* Return true if OP refers to small data symbols directly. */ |
| |
| bool |
| loongarch_small_data_pattern_p (rtx op) |
| { |
| return loongarch_small_data_pattern_1 (op); |
| } |
| |
| /* Rewrite *LOC so that it refers to small data using explicit |
| relocations. */ |
| |
| static void |
| loongarch_rewrite_small_data_1 (rtx *loc) |
| { |
| subrtx_ptr_iterator::array_type array; |
| FOR_EACH_SUBRTX_PTR (iter, array, loc, ALL) |
| { |
| rtx *loc = *iter; |
| if (MEM_P (*loc)) |
| { |
| loongarch_rewrite_small_data_1 (&XEXP (*loc, 0)); |
| iter.skip_subrtxes (); |
| } |
| } |
| } |
| |
| /* Rewrite instruction pattern PATTERN so that it refers to small data |
| using explicit relocations. */ |
| |
| rtx |
| loongarch_rewrite_small_data (rtx pattern) |
| { |
| pattern = copy_insn (pattern); |
| loongarch_rewrite_small_data_1 (&pattern); |
| return pattern; |
| } |
| |
| /* The cost of loading values from the constant pool. It should be |
| larger than the cost of any constant we want to synthesize inline. */ |
| #define CONSTANT_POOL_COST COSTS_N_INSNS (8) |
| |
| /* Return true if there is a instruction that implements CODE |
| and if that instruction accepts X as an immediate operand. */ |
| |
| static int |
| loongarch_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 ROTATE: |
| case ROTATERT: |
| return true; |
| |
| case AND: |
| case IOR: |
| case XOR: |
| /* These instructions take 12-bit unsigned immediates. */ |
| return IMM12_OPERAND_UNSIGNED (x); |
| |
| case PLUS: |
| case LT: |
| case LTU: |
| /* These instructions take 12-bit signed immediates. */ |
| return IMM12_OPERAND (x); |
| |
| case EQ: |
| case NE: |
| case GT: |
| case GTU: |
| /* The "immediate" forms of these instructions are really |
| implemented as comparisons with register 0. */ |
| return x == 0; |
| |
| case GE: |
| case GEU: |
| /* Likewise, meaning that the only valid immediate operand is 1. */ |
| return x == 1; |
| |
| case LE: |
| /* We add 1 to the immediate and use SLT. */ |
| return IMM12_OPERAND (x + 1); |
| |
| case LEU: |
| /* Likewise SLTU, but reject the always-true case. */ |
| return IMM12_OPERAND (x + 1) && x + 1 != 0; |
| |
| case SIGN_EXTRACT: |
| case ZERO_EXTRACT: |
| /* The bit position and size are immediate operands. */ |
| return 1; |
| |
| default: |
| /* By default assume that $0 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 has cost SINGLE_COST |
| and that the sequence of a double-word operation has cost DOUBLE_COST. |
| If SPEED is true, optimize for speed otherwise optimize for size. */ |
| |
| static int |
| loongarch_binary_cost (rtx x, int single_cost, int double_cost, bool speed) |
| { |
| int cost; |
| |
| if (GET_MODE_SIZE (GET_MODE (x)) == UNITS_PER_WORD * 2) |
| cost = double_cost; |
| else |
| cost = single_cost; |
| return (cost |
| + set_src_cost (XEXP (x, 0), GET_MODE (x), speed) |
| + rtx_cost (XEXP (x, 1), GET_MODE (x), GET_CODE (x), 1, speed)); |
| } |
| |
| /* Return the cost of floating-point multiplications of mode MODE. */ |
| |
| static int |
| loongarch_fp_mult_cost (machine_mode mode) |
| { |
| return mode == DFmode ? loongarch_cost->fp_mult_df |
| : loongarch_cost->fp_mult_sf; |
| } |
| |
| /* Return the cost of floating-point divisions of mode MODE. */ |
| |
| static int |
| loongarch_fp_div_cost (machine_mode mode) |
| { |
| return mode == DFmode ? loongarch_cost->fp_div_df |
| : loongarch_cost->fp_div_sf; |
| } |
| |
| /* Return the cost of sign-extending OP to mode MODE, not including the |
| cost of OP itself. */ |
| |
| static int |
| loongarch_sign_extend_cost (rtx op) |
| { |
| if (MEM_P (op)) |
| /* Extended loads are as cheap as unextended ones. */ |
| return 0; |
| |
| return COSTS_N_INSNS (1); |
| } |
| |
| /* Return the cost of zero-extending OP to mode MODE, not including the |
| cost of OP itself. */ |
| |
| static int |
| loongarch_zero_extend_cost (rtx op) |
| { |
| if (MEM_P (op)) |
| /* Extended loads are as cheap as unextended ones. */ |
| return 0; |
| |
| /* We can use ANDI. */ |
| return COSTS_N_INSNS (1); |
| } |
| |
| /* Return the cost of moving between two registers of mode MODE, |
| assuming that the move will be in pieces of at most UNITS bytes. */ |
| |
| static int |
| loongarch_set_reg_reg_piece_cost (machine_mode mode, unsigned int units) |
| { |
| return COSTS_N_INSNS ((GET_MODE_SIZE (mode) + units - 1) / units); |
| } |
| |
| /* Return the cost of moving between two registers of mode MODE. */ |
| |
| static int |
| loongarch_set_reg_reg_cost (machine_mode mode) |
| { |
| switch (GET_MODE_CLASS (mode)) |
| { |
| case MODE_CC: |
| return loongarch_set_reg_reg_piece_cost (mode, GET_MODE_SIZE (CCmode)); |
| |
| case MODE_FLOAT: |
| case MODE_COMPLEX_FLOAT: |
| case MODE_VECTOR_FLOAT: |
| if (TARGET_HARD_FLOAT) |
| return loongarch_set_reg_reg_piece_cost (mode, UNITS_PER_HWFPVALUE); |
| /* Fall through. */ |
| |
| default: |
| return loongarch_set_reg_reg_piece_cost (mode, UNITS_PER_WORD); |
| } |
| } |
| |
| /* Implement TARGET_RTX_COSTS. */ |
| |
| static bool |
| loongarch_rtx_costs (rtx x, machine_mode mode, int outer_code, |
| int opno ATTRIBUTE_UNUSED, int *total, bool speed) |
| { |
| int code = GET_CODE (x); |
| bool float_mode_p = FLOAT_MODE_P (mode); |
| int cost; |
| rtx addr; |
| |
| if (outer_code == COMPARE) |
| { |
| gcc_assert (CONSTANT_P (x)); |
| *total = 0; |
| return true; |
| } |
| |
| switch (code) |
| { |
| case CONST_INT: |
| if (TARGET_64BIT && outer_code == AND && UINTVAL (x) == 0xffffffff) |
| { |
| *total = 0; |
| return true; |
| } |
| |
| /* When not optimizing for size, we care more about the cost |
| of hot code, and hot code is often in a loop. If a constant |
| operand needs to be forced into a register, we will often be |
| able to hoist the constant load out of the loop, so the load |
| should not contribute to the cost. */ |
| if (speed || loongarch_immediate_operand_p (outer_code, INTVAL (x))) |
| { |
| *total = 0; |
| return true; |
| } |
| /* Fall through. */ |
| |
| case CONST: |
| case SYMBOL_REF: |
| case LABEL_REF: |
| case CONST_DOUBLE: |
| cost = loongarch_const_insns (x); |
| if (cost > 0) |
| { |
| if (cost == 1 && outer_code == SET |
| && !(float_mode_p && TARGET_HARD_FLOAT)) |
| cost = 0; |
| else if ((outer_code == SET || GET_MODE (x) == VOIDmode)) |
| cost = 1; |
| *total = COSTS_N_INSNS (cost); |
| return true; |
| } |
| /* The value will need to be fetched from the constant pool. */ |
| *total = CONSTANT_POOL_COST; |
| return true; |
| |
| case MEM: |
| /* If the address is legitimate, return the number of |
| instructions it needs. */ |
| addr = XEXP (x, 0); |
| /* Check for a scaled indexed address. */ |
| if (loongarch_index_address_p (addr, mode)) |
| { |
| *total = COSTS_N_INSNS (2); |
| return true; |
| } |
| cost = loongarch_address_insns (addr, mode, true); |
| if (cost > 0) |
| { |
| *total = COSTS_N_INSNS (cost + 1); |
| return true; |
| } |
| /* Otherwise use the default handling. */ |
| return false; |
| |
| case FFS: |
| *total = COSTS_N_INSNS (6); |
| return false; |
| |
| case NOT: |
| *total = COSTS_N_INSNS (GET_MODE_SIZE (mode) > UNITS_PER_WORD ? 2 : 1); |
| return false; |
| |
| case AND: |
| /* Check for a *clear_upper32 pattern and treat it like a zero |
| extension. See the pattern's comment for details. */ |
| if (TARGET_64BIT && mode == DImode && CONST_INT_P (XEXP (x, 1)) |
| && UINTVAL (XEXP (x, 1)) == 0xffffffff) |
| { |
| *total = (loongarch_zero_extend_cost (XEXP (x, 0)) |
| + set_src_cost (XEXP (x, 0), mode, speed)); |
| return true; |
| } |
| /* (AND (NOT op0) (NOT op1) is a nor operation that can be done in |
| a single instruction. */ |
| if (GET_CODE (XEXP (x, 0)) == NOT && GET_CODE (XEXP (x, 1)) == NOT) |
| { |
| cost = GET_MODE_SIZE (mode) > UNITS_PER_WORD ? 2 : 1; |
| *total = (COSTS_N_INSNS (cost) |
| + set_src_cost (XEXP (XEXP (x, 0), 0), mode, speed) |
| + set_src_cost (XEXP (XEXP (x, 1), 0), mode, speed)); |
| return true; |
| } |
| |
| /* Fall through. */ |
| |
| case IOR: |
| case XOR: |
| /* Double-word operations use two single-word operations. */ |
| *total = loongarch_binary_cost (x, COSTS_N_INSNS (1), COSTS_N_INSNS (2), |
| speed); |
| return true; |
| |
| case ASHIFT: |
| case ASHIFTRT: |
| case LSHIFTRT: |
| case ROTATE: |
| case ROTATERT: |
| if (CONSTANT_P (XEXP (x, 1))) |
| *total = loongarch_binary_cost (x, COSTS_N_INSNS (1), |
| COSTS_N_INSNS (4), speed); |
| else |
| *total = loongarch_binary_cost (x, COSTS_N_INSNS (1), |
| COSTS_N_INSNS (12), speed); |
| return true; |
| |
| case ABS: |
| if (float_mode_p) |
| *total = loongarch_cost->fp_add; |
| else |
| *total = COSTS_N_INSNS (4); |
| return false; |
| |
| case LT: |
| case LTU: |
| case LE: |
| case LEU: |
| case GT: |
| case GTU: |
| case GE: |
| case GEU: |
| case EQ: |
| case NE: |
| case UNORDERED: |
| case LTGT: |
| case UNGE: |
| case UNGT: |
| case UNLE: |
| case UNLT: |
| /* Branch comparisons have VOIDmode, so use the first operand's |
| mode instead. */ |
| mode = GET_MODE (XEXP (x, 0)); |
| if (FLOAT_MODE_P (mode)) |
| { |
| *total = loongarch_cost->fp_add; |
| return false; |
| } |
| *total = loongarch_binary_cost (x, COSTS_N_INSNS (1), COSTS_N_INSNS (4), |
| speed); |
| return true; |
| |
| case MINUS: |
| case PLUS: |
| if (float_mode_p) |
| { |
| *total = loongarch_cost->fp_add; |
| return false; |
| } |
| |
| /* If it's an add + mult (which is equivalent to shift left) and |
| it's immediate operand satisfies const_immalsl_operand predicate. */ |
| if ((mode == SImode || (TARGET_64BIT && mode == DImode)) |
| && GET_CODE (XEXP (x, 0)) == MULT) |
| { |
| rtx op2 = XEXP (XEXP (x, 0), 1); |
| if (const_immalsl_operand (op2, mode)) |
| { |
| *total = (COSTS_N_INSNS (1) |
| + set_src_cost (XEXP (XEXP (x, 0), 0), mode, speed) |
| + set_src_cost (XEXP (x, 1), mode, speed)); |
| return true; |
| } |
| } |
| |
| /* Double-word operations require three single-word operations and |
| an SLTU. */ |
| *total = loongarch_binary_cost (x, COSTS_N_INSNS (1), COSTS_N_INSNS (4), |
| speed); |
| return true; |
| |
| case NEG: |
| if (float_mode_p) |
| *total = loongarch_cost->fp_add; |
| else |
| *total = COSTS_N_INSNS (GET_MODE_SIZE (mode) > UNITS_PER_WORD ? 4 : 1); |
| return false; |
| |
| case FMA: |
| *total = loongarch_fp_mult_cost (mode); |
| return false; |
| |
| case MULT: |
| if (float_mode_p) |
| *total = loongarch_fp_mult_cost (mode); |
| else if (mode == DImode && !TARGET_64BIT) |
| *total = (speed |
| ? loongarch_cost->int_mult_si * 3 + 6 |
| : COSTS_N_INSNS (7)); |
| else if (!speed) |
| *total = COSTS_N_INSNS (1) + 1; |
| else if (mode == DImode) |
| *total = loongarch_cost->int_mult_di; |
| else |
| *total = loongarch_cost->int_mult_si; |
| return false; |
| |
| case DIV: |
| /* Check for a reciprocal. */ |
| if (float_mode_p |
| && flag_unsafe_math_optimizations |
| && XEXP (x, 0) == CONST1_RTX (mode)) |
| { |
| if (outer_code == SQRT || GET_CODE (XEXP (x, 1)) == SQRT) |
| /* An rsqrt<mode>a or rsqrt<mode>b pattern. Count the |
| division as being free. */ |
| *total = set_src_cost (XEXP (x, 1), mode, speed); |
| else |
| *total = (loongarch_fp_div_cost (mode) |
| + set_src_cost (XEXP (x, 1), mode, speed)); |
| return true; |
| } |
| /* Fall through. */ |
| |
| case SQRT: |
| case MOD: |
| if (float_mode_p) |
| { |
| *total = loongarch_fp_div_cost (mode); |
| return false; |
| } |
| /* Fall through. */ |
| |
| case UDIV: |
| case UMOD: |
| if (!speed) |
| { |
| *total = COSTS_N_INSNS (loongarch_idiv_insns (mode)); |
| } |
| else if (mode == DImode) |
| *total = loongarch_cost->int_div_di; |
| else |
| *total = loongarch_cost->int_div_si; |
| return false; |
| |
| case SIGN_EXTEND: |
| *total = loongarch_sign_extend_cost (XEXP (x, 0)); |
| return false; |
| |
| case ZERO_EXTEND: |
| *total = loongarch_zero_extend_cost (XEXP (x, 0)); |
| return false; |
| case TRUNCATE: |
| /* Costings for highpart multiplies. Matching patterns of the form: |
| |
| (lshiftrt:DI (mult:DI (sign_extend:DI (...) |
| (sign_extend:DI (...)) |
| (const_int 32) |
| */ |
| if ((GET_CODE (XEXP (x, 0)) == ASHIFTRT |
| || GET_CODE (XEXP (x, 0)) == LSHIFTRT) |
| && CONST_INT_P (XEXP (XEXP (x, 0), 1)) |
| && ((INTVAL (XEXP (XEXP (x, 0), 1)) == 32 |
| && GET_MODE (XEXP (x, 0)) == DImode) |
| || (TARGET_64BIT |
| && INTVAL (XEXP (XEXP (x, 0), 1)) == 64 |
| && GET_MODE (XEXP (x, 0)) == TImode)) |
| && GET_CODE (XEXP (XEXP (x, 0), 0)) == MULT |
| && ((GET_CODE (XEXP (XEXP (XEXP (x, 0), 0), 0)) == SIGN_EXTEND |
| && GET_CODE (XEXP (XEXP (XEXP (x, 0), 0), 1)) == SIGN_EXTEND) |
| || (GET_CODE (XEXP (XEXP (XEXP (x, 0), 0), 0)) == ZERO_EXTEND |
| && (GET_CODE (XEXP (XEXP (XEXP (x, 0), 0), 1)) |
| == ZERO_EXTEND)))) |
| { |
| if (!speed) |
| *total = COSTS_N_INSNS (1) + 1; |
| else if (mode == DImode) |
| *total = loongarch_cost->int_mult_di; |
| else |
| *total = loongarch_cost->int_mult_si; |
| |
| /* Sign extension is free, zero extension costs for DImode when |
| on a 64bit core / when DMUL is present. */ |
| for (int i = 0; i < 2; ++i) |
| { |
| rtx op = XEXP (XEXP (XEXP (x, 0), 0), i); |
| if (TARGET_64BIT |
| && GET_CODE (op) == ZERO_EXTEND |
| && GET_MODE (op) == DImode) |
| *total += rtx_cost (op, DImode, MULT, i, speed); |
| else |
| *total += rtx_cost (XEXP (op, 0), VOIDmode, GET_CODE (op), 0, |
| speed); |
| } |
| |
| return true; |
| } |
| return false; |
| |
| case FLOAT: |
| case UNSIGNED_FLOAT: |
| case FIX: |
| case FLOAT_EXTEND: |
| case FLOAT_TRUNCATE: |
| *total = loongarch_cost->fp_add; |
| return false; |
| |
| case SET: |
| if (register_operand (SET_DEST (x), VOIDmode) |
| && reg_or_0_operand (SET_SRC (x), VOIDmode)) |
| { |
| *total = loongarch_set_reg_reg_cost (GET_MODE (SET_DEST (x))); |
| return true; |
| } |
| return false; |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* Implement TARGET_ADDRESS_COST. */ |
| |
| static int |
| loongarch_address_cost (rtx addr, machine_mode mode, |
| addr_space_t as ATTRIBUTE_UNUSED, |
| bool speed ATTRIBUTE_UNUSED) |
| { |
| return loongarch_address_insns (addr, mode, false); |
| } |
| |
| /* Return one word of double-word value OP, taking into account the fixed |
| endianness of certain registers. HIGH_P is true to select the high part, |
| false to select the low part. */ |
| |
| rtx |
| loongarch_subword (rtx op, bool high_p) |
| { |
| unsigned int byte; |
| machine_mode mode; |
| |
| byte = high_p ? UNITS_PER_WORD : 0; |
| mode = GET_MODE (op); |
| if (mode == VOIDmode) |
| mode = TARGET_64BIT ? TImode : DImode; |
| |
| if (FP_REG_RTX_P (op)) |
| return gen_rtx_REG (word_mode, REGNO (op) + high_p); |
| |
| if (MEM_P (op)) |
| return loongarch_rewrite_small_data (adjust_address (op, word_mode, byte)); |
| |
| return simplify_gen_subreg (word_mode, op, mode, byte); |
| } |
| |
| /* Return true if a move from SRC to DEST should be split into two. |
| SPLIT_TYPE describes the split condition. */ |
| |
| bool |
| loongarch_split_move_p (rtx dest, rtx src) |
| { |
| /* FPR-to-FPR moves can be done in a single instruction, if they're |
| allowed at all. */ |
| unsigned int size = GET_MODE_SIZE (GET_MODE (dest)); |
| if (size == 8 && FP_REG_RTX_P (src) && FP_REG_RTX_P (dest)) |
| return false; |
| |
| /* Check for floating-point loads and stores. */ |
| if (size == 8) |
| { |
| if (FP_REG_RTX_P (dest) && MEM_P (src)) |
| return false; |
| if (FP_REG_RTX_P (src) && MEM_P (dest)) |
| return false; |
| } |
| /* Otherwise split all multiword moves. */ |
| return size > UNITS_PER_WORD; |
| } |
| |
| /* Split a move from SRC to DEST, given that loongarch_split_move_p holds. |
| SPLIT_TYPE describes the split condition. */ |
| |
| void |
| loongarch_split_move (rtx dest, rtx src, rtx insn_) |
| { |
| rtx low_dest; |
| |
| gcc_checking_assert (loongarch_split_move_p (dest, src)); |
| if (FP_REG_RTX_P (dest) || FP_REG_RTX_P (src)) |
| { |
| if (!TARGET_64BIT && GET_MODE (dest) == DImode) |
| emit_insn (gen_move_doubleword_fprdi (dest, src)); |
| else if (!TARGET_64BIT && GET_MODE (dest) == DFmode) |
| emit_insn (gen_move_doubleword_fprdf (dest, src)); |
| else if (TARGET_64BIT && GET_MODE (dest) == TFmode) |
| emit_insn (gen_move_doubleword_fprtf (dest, src)); |
| else |
| gcc_unreachable (); |
| } |
| else |
| { |
| /* The operation can be split into two normal moves. Decide in |
| which order to do them. */ |
| low_dest = loongarch_subword (dest, false); |
| if (REG_P (low_dest) && reg_overlap_mentioned_p (low_dest, src)) |
| { |
| loongarch_emit_move (loongarch_subword (dest, true), |
| loongarch_subword (src, true)); |
| loongarch_emit_move (low_dest, loongarch_subword (src, false)); |
| } |
| else |
| { |
| loongarch_emit_move (low_dest, loongarch_subword (src, false)); |
| loongarch_emit_move (loongarch_subword (dest, true), |
| loongarch_subword (src, true)); |
| } |
| } |
| |
| /* This is a hack. See if the next insn uses DEST and if so, see if we |
| can forward SRC for DEST. This is most useful if the next insn is a |
| simple store. */ |
| rtx_insn *insn = (rtx_insn *) insn_; |
| struct loongarch_address_info addr = {}; |
| if (insn) |
| { |
| rtx_insn *next = next_nonnote_nondebug_insn_bb (insn); |
| if (next) |
| { |
| rtx set = single_set (next); |
| if (set && SET_SRC (set) == dest) |
| { |
| if (MEM_P (src)) |
| { |
| rtx tmp = XEXP (src, 0); |
| loongarch_classify_address (&addr, tmp, GET_MODE (tmp), |
| true); |
| if (addr.reg && !reg_overlap_mentioned_p (dest, addr.reg)) |
| validate_change (next, &SET_SRC (set), src, false); |
| } |
| else |
| validate_change (next, &SET_SRC (set), src, false); |
| } |
| } |
| } |
| } |
| |
| /* Return true if a move from SRC to DEST in INSN should be split. */ |
| |
| static bool |
| loongarch_split_move_insn_p (rtx dest, rtx src) |
| { |
| return loongarch_split_move_p (dest, src); |
| } |
| |
| /* Implement TARGET_CONSTANT_ALIGNMENT. */ |
| |
| static HOST_WIDE_INT |
| loongarch_constant_alignment (const_tree exp, HOST_WIDE_INT align) |
| { |
| if (TREE_CODE (exp) == STRING_CST || TREE_CODE (exp) == CONSTRUCTOR) |
| return MAX (align, BITS_PER_WORD); |
| return align; |
| } |
| |
| const char * |
| loongarch_output_move_index (rtx x, machine_mode mode, bool ldr) |
| { |
| int index = exact_log2 (GET_MODE_SIZE (mode)); |
| if (!IN_RANGE (index, 0, 3)) |
| return NULL; |
| |
| struct loongarch_address_info info; |
| if ((loongarch_classify_address (&info, x, mode, false) |
| && !(info.type == ADDRESS_REG_REG)) |
| || !loongarch_legitimate_address_p (mode, x, false)) |
| return NULL; |
| |
| const char *const insn[][4] = |
| { |
| { |
| "stx.b\t%z1,%0", |
| "stx.h\t%z1,%0", |
| "stx.w\t%z1,%0", |
| "stx.d\t%z1,%0", |
| }, |
| { |
| "ldx.bu\t%0,%1", |
| "ldx.hu\t%0,%1", |
| "ldx.w\t%0,%1", |
| "ldx.d\t%0,%1", |
| } |
| }; |
| |
| return insn[ldr][index]; |
| } |
| |
| const char * |
| loongarch_output_move_index_float (rtx x, machine_mode mode, bool ldr) |
| { |
| int index = exact_log2 (GET_MODE_SIZE (mode)); |
| if (!IN_RANGE (index, 2, 3)) |
| return NULL; |
| |
| struct loongarch_address_info info; |
| if ((loongarch_classify_address (&info, x, mode, false) |
| && !(info.type == ADDRESS_REG_REG)) |
| || !loongarch_legitimate_address_p (mode, x, false)) |
| return NULL; |
| |
| const char *const insn[][2] = |
| { |
| { |
| "fstx.s\t%1,%0", |
| "fstx.d\t%1,%0" |
| }, |
| { |
| "fldx.s\t%0,%1", |
| "fldx.d\t%0,%1" |
| }, |
| }; |
| |
| return insn[ldr][index-2]; |
| } |
| |
| /* Return the appropriate instructions to move SRC into DEST. Assume |
| that SRC is operand 1 and DEST is operand 0. */ |
| |
| const char * |
| loongarch_output_move (rtx dest, rtx src) |
| { |
| enum rtx_code dest_code = GET_CODE (dest); |
| enum rtx_code src_code = GET_CODE (src); |
| machine_mode mode = GET_MODE (dest); |
| bool dbl_p = (GET_MODE_SIZE (mode) == 8); |
| |
| if (loongarch_split_move_p (dest, src)) |
| return "#"; |
| |
| if ((src_code == REG && GP_REG_P (REGNO (src))) |
| || (src == CONST0_RTX (mode))) |
| { |
| if (dest_code == REG) |
| { |
| if (GP_REG_P (REGNO (dest))) |
| return "or\t%0,%z1,$r0"; |
| |
| if (FP_REG_P (REGNO (dest))) |
| return dbl_p ? "movgr2fr.d\t%0,%z1" : "movgr2fr.w\t%0,%z1"; |
| } |
| if (dest_code == MEM) |
| { |
| const char *insn = NULL; |
| insn = loongarch_output_move_index (XEXP (dest, 0), GET_MODE (dest), |
| false); |
| if (insn) |
| return insn; |
| |
| rtx offset = XEXP (dest, 0); |
| if (GET_CODE (offset) == PLUS) |
| offset = XEXP (offset, 1); |
| switch (GET_MODE_SIZE (mode)) |
| { |
| case 1: |
| return "st.b\t%z1,%0"; |
| case 2: |
| return "st.h\t%z1,%0"; |
| case 4: |
| /* Matching address type with a 12bit offset and |
| ADDRESS_LO_SUM. */ |
| if (const_arith_operand (offset, Pmode) |
| || GET_CODE (offset) == LO_SUM) |
| return "st.w\t%z1,%0"; |
| else |
| return "stptr.w\t%z1,%0"; |
| case 8: |
| if (const_arith_operand (offset, Pmode) |
| || GET_CODE (offset) == LO_SUM) |
| return "st.d\t%z1,%0"; |
| else |
| return "stptr.d\t%z1,%0"; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| } |
| if (dest_code == REG && GP_REG_P (REGNO (dest))) |
| { |
| if (src_code == REG) |
| if (FP_REG_P (REGNO (src))) |
| return dbl_p ? "movfr2gr.d\t%0,%1" : "movfr2gr.s\t%0,%1"; |
| |
| if (src_code == MEM) |
| { |
| const char *insn = NULL; |
| insn = loongarch_output_move_index (XEXP (src, 0), GET_MODE (src), |
| true); |
| if (insn) |
| return insn; |
| |
| rtx offset = XEXP (src, 0); |
| if (GET_CODE (offset) == PLUS) |
| offset = XEXP (offset, 1); |
| switch (GET_MODE_SIZE (mode)) |
| { |
| case 1: |
| return "ld.bu\t%0,%1"; |
| case 2: |
| return "ld.hu\t%0,%1"; |
| case 4: |
| /* Matching address type with a 12bit offset and |
| ADDRESS_LO_SUM. */ |
| if (const_arith_operand (offset, Pmode) |
| || GET_CODE (offset) == LO_SUM) |
| return "ld.w\t%0,%1"; |
| else |
| return "ldptr.w\t%0,%1"; |
| case 8: |
| if (const_arith_operand (offset, Pmode) |
| || GET_CODE (offset) == LO_SUM) |
| return "ld.d\t%0,%1"; |
| else |
| return "ldptr.d\t%0,%1"; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| if (src_code == HIGH) |
| { |
| rtx offset, x; |
| split_const (XEXP (src, 0), &x, &offset); |
| enum loongarch_symbol_type type = SYMBOL_PCREL; |
| |
| if (UNSPEC_ADDRESS_P (x)) |
| type = UNSPEC_ADDRESS_TYPE (x); |
| |
| if (type == SYMBOL_TLS_LE) |
| return "lu12i.w\t%0,%h1"; |
| else |
| return "pcalau12i\t%0,%h1"; |
| } |
| |
| if (src_code == CONST_INT) |
| { |
| if (LU12I_INT (src)) |
| return "lu12i.w\t%0,%1>>12\t\t\t# %X1"; |
| else if (IMM12_INT (src)) |
| return "addi.w\t%0,$r0,%1\t\t\t# %X1"; |
| else if (IMM12_INT_UNSIGNED (src)) |
| return "ori\t%0,$r0,%1\t\t\t# %X1"; |
| else if (LU52I_INT (src)) |
| return "lu52i.d\t%0,$r0,%X1>>52\t\t\t# %1"; |
| else |
| gcc_unreachable (); |
| } |
| } |
| |
| if (!TARGET_EXPLICIT_RELOCS |
| && dest_code == REG && symbolic_operand (src, VOIDmode)) |
| { |
| if (loongarch_classify_symbol (src) == SYMBOL_PCREL) |
| return "la.local\t%0,%1"; |
| else |
| return "la.global\t%0,%1"; |
| } |
| |
| if (src_code == REG && FP_REG_P (REGNO (src))) |
| { |
| if (dest_code == REG && FP_REG_P (REGNO (dest))) |
| return dbl_p ? "fmov.d\t%0,%1" : "fmov.s\t%0,%1"; |
| |
| if (dest_code == MEM) |
| { |
| const char *insn = NULL; |
| insn = loongarch_output_move_index_float (XEXP (dest, 0), |
| GET_MODE (dest), |
| false); |
| if (insn) |
| return insn; |
| |
| return dbl_p ? "fst.d\t%1,%0" : "fst.s\t%1,%0"; |
| } |
| } |
| |
| if (dest_code == REG && FP_REG_P (REGNO (dest))) |
| { |
| if (src_code == MEM) |
| { |
| const char *insn = NULL; |
| insn = loongarch_output_move_index_float (XEXP (src, 0), |
| GET_MODE (src), |
| true); |
| if (insn) |
| return insn; |
| |
| return dbl_p ? "fld.d\t%0,%1" : "fld.s\t%0,%1"; |
| } |
| } |
| |
| gcc_unreachable (); |
| } |
| |
| /* Return true if CMP1 is a suitable second operand for integer ordering |
| test CODE. */ |
| |
| static bool |
| loongarch_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 |
| loongarch_canonicalize_int_order_test (enum rtx_code *code, rtx *cmp1, |
| machine_mode mode) |
| { |
| HOST_WIDE_INT plus_one; |
| |
| if (loongarch_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 |
| loongarch_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 LoongArch 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 (loongarch_canonicalize_int_order_test (&code, &cmp1, mode)) |
| loongarch_emit_binary (code, target, cmp0, cmp1); |
| else |
| { |
| enum rtx_code inv_code = reverse_condition (code); |
| if (!loongarch_canonicalize_int_order_test (&inv_code, &cmp1, mode)) |
| { |
| cmp1 = force_reg (mode, cmp1); |
| loongarch_emit_int_order_test (code, invert_ptr, target, cmp0, cmp1); |
| } |
| else if (invert_ptr == 0) |
| { |
| rtx inv_target; |
| |
| inv_target = loongarch_force_binary (GET_MODE (target), |
| inv_code, cmp0, cmp1); |
| loongarch_emit_binary (XOR, target, inv_target, const1_rtx); |
| } |
| else |
| { |
| *invert_ptr = !*invert_ptr; |
| loongarch_emit_binary (inv_code, target, cmp0, cmp1); |
| } |
| } |
| } |
| |
| /* Return a register that is zero if CMP0 and CMP1 are equal. |
| The register will have the same mode as CMP0. */ |
| |
| static rtx |
| loongarch_zero_if_equal (rtx cmp0, rtx cmp1) |
| { |
| if (cmp1 == const0_rtx) |
| return cmp0; |
| |
| if (uns_arith_operand (cmp1, VOIDmode)) |
| return expand_binop (GET_MODE (cmp0), xor_optab, cmp0, cmp1, 0, 0, |
| OPTAB_DIRECT); |
| |
| return expand_binop (GET_MODE (cmp0), sub_optab, cmp0, cmp1, 0, 0, |
| OPTAB_DIRECT); |
| } |
| |
| /* Allocate a floating-point condition-code register of mode MODE. */ |
| |
| static rtx |
| loongarch_allocate_fcc (machine_mode mode) |
| { |
| unsigned int regno, count; |
| |
| gcc_assert (TARGET_HARD_FLOAT); |
| |
| if (mode == FCCmode) |
| count = 1; |
| else |
| gcc_unreachable (); |
| |
| cfun->machine->next_fcc += -cfun->machine->next_fcc & (count - 1); |
| if (cfun->machine->next_fcc > FCC_REG_LAST - FCC_REG_FIRST) |
| cfun->machine->next_fcc = 0; |
| |
| regno = FCC_REG_FIRST + cfun->machine->next_fcc; |
| cfun->machine->next_fcc += count; |
| return gen_rtx_REG (mode, regno); |
| } |
| |
| /* Sign- or zero-extend OP0 and OP1 for integer comparisons. */ |
| |
| static void |
| loongarch_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))) |
| { |
| /* TODO: checkout It is more profitable to zero-extend QImode values. */ |
| if (unsigned_condition (code) == code && GET_MODE (*op0) == QImode) |
| { |
| *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 |
| loongarch_emit_int_compare (enum rtx_code *code, rtx *op0, rtx *op1) |
| { |
| static const enum rtx_code |
| mag_comparisons[][2] = {{LEU, LTU}, {GTU, GEU}, {LE, LT}, {GT, GE}}; |
| |
| 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 (IMM12_OPERAND (-rhs)) |
| { |
| *op0 = loongarch_force_binary (GET_MODE (*op0), PLUS, *op0, |
| GEN_INT (-rhs)); |
| *op1 = const0_rtx; |
| } |
| } |
| else |
| { |
| /* 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; |
| |
| if ((increment && rhs == HOST_WIDE_INT_MAX) |
| || (decrement && rhs == HOST_WIDE_INT_MIN)) |
| break; |
| |
| new_rhs = rhs + (increment ? 1 : -1); |
| if (loongarch_integer_cost (new_rhs) |
| < loongarch_integer_cost (rhs)) |
| { |
| *op1 = GEN_INT (new_rhs); |
| *code = mag_comparisons[i][increment]; |
| } |
| break; |
| } |
| } |
| } |
| |
| loongarch_extend_comparands (*code, op0, op1); |
| |
| *op0 = force_reg (word_mode, *op0); |
| if (*op1 != const0_rtx) |
| *op1 = force_reg (word_mode, *op1); |
| } |
| |
| /* Like loongarch_emit_int_compare, but for floating-point comparisons. */ |
| |
| static void |
| loongarch_emit_float_compare (enum rtx_code *code, rtx *op0, rtx *op1) |
| { |
| rtx cmp_op0 = *op0; |
| rtx cmp_op1 = *op1; |
| |
| /* Floating-point tests use a separate FCMP.cond.fmt |
| comparison to set a register. The branch or conditional move will |
| then compare that register against zero. |
| |
| Set CMP_CODE to the code of the comparison instruction and |
| *CODE to the code that the branch or move should use. */ |
| enum rtx_code cmp_code = *code; |
| /* Three FP conditions cannot be implemented by reversing the |
| operands for FCMP.cond.fmt, instead a reversed condition code is |
| required and a test for false. */ |
| *code = NE; |
| *op0 = loongarch_allocate_fcc (FCCmode); |
| |
| *op1 = const0_rtx; |
| loongarch_emit_binary (cmp_code, *op0, cmp_op0, cmp_op1); |
| } |
| |
| /* Try performing the comparison in OPERANDS[1], whose arms are OPERANDS[2] |
| and OPERAND[3]. Store the result in OPERANDS[0]. |
| |
| On 64-bit targets, the mode of the comparison and target will always be |
| SImode, thus possibly narrower than that of the comparison's operands. */ |
| |
| void |
| loongarch_expand_scc (rtx operands[]) |
| { |
| rtx target = operands[0]; |
| enum rtx_code code = GET_CODE (operands[1]); |
| rtx op0 = operands[2]; |
| rtx op1 = operands[3]; |
| |
| loongarch_extend_comparands (code, &op0, &op1); |
| op0 = force_reg (word_mode, op0); |
| |
| gcc_assert (GET_MODE_CLASS (GET_MODE (op0)) == MODE_INT); |
| |
| if (code == EQ || code == NE) |
| { |
| rtx zie = loongarch_zero_if_equal (op0, op1); |
| loongarch_emit_binary (code, target, zie, const0_rtx); |
| } |
| else |
| loongarch_emit_int_order_test (code, 0, target, op0, op1); |
| } |
| |
| /* Compare OPERANDS[1] with OPERANDS[2] using comparison code |
| CODE and jump to OPERANDS[3] if the condition holds. */ |
| |
| void |
| loongarch_expand_conditional_branch (rtx *operands) |
| { |
| enum rtx_code code = GET_CODE (operands[0]); |
| rtx op0 = operands[1]; |
| rtx op1 = operands[2]; |
| rtx condition; |
| |
| if (FLOAT_MODE_P (GET_MODE (op1))) |
| loongarch_emit_float_compare (&code, &op0, &op1); |
| else |
| loongarch_emit_int_compare (&code, &op0, &op1); |
| |
| condition = gen_rtx_fmt_ee (code, VOIDmode, op0, op1); |
| emit_jump_insn (gen_condjump (condition, operands[3])); |
| } |
| |
| /* Perform the comparison in OPERANDS[1]. Move OPERANDS[2] into OPERANDS[0] |
| if the condition holds, otherwise move OPERANDS[3] into OPERANDS[0]. */ |
| |
| void |
| loongarch_expand_conditional_move (rtx *operands) |
| { |
| enum rtx_code code = GET_CODE (operands[1]); |
| rtx op0 = XEXP (operands[1], 0); |
| rtx op1 = XEXP (operands[1], 1); |
| |
| if (FLOAT_MODE_P (GET_MODE (op1))) |
| loongarch_emit_float_compare (&code, &op0, &op1); |
| else |
| { |
| loongarch_extend_comparands (code, &op0, &op1); |
| |
| op0 = force_reg (word_mode, op0); |
| |
| if (code == EQ || code == NE) |
| { |
| op0 = loongarch_zero_if_equal (op0, op1); |
| op1 = const0_rtx; |
| } |
| else |
| { |
| /* The comparison needs a separate scc instruction. Store the |
| result of the scc in *OP0 and compare it against zero. */ |
| bool invert = false; |
| rtx target = gen_reg_rtx (GET_MODE (op0)); |
| loongarch_emit_int_order_test (code, &invert, target, op0, op1); |
| code = invert ? EQ : NE; |
| op0 = target; |
| op1 = const0_rtx; |
| } |
| } |
| |
| rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1); |
| /* There is no direct support for general conditional GP move involving |
| two registers using SEL. */ |
| if (INTEGRAL_MODE_P (GET_MODE (operands[2])) |
| && register_operand (operands[2], VOIDmode) |
| && register_operand (operands[3], VOIDmode)) |
| { |
| machine_mode mode = GET_MODE (operands[0]); |
| rtx temp = gen_reg_rtx (mode); |
| rtx temp2 = gen_reg_rtx (mode); |
| |
| emit_insn (gen_rtx_SET (temp, |
| gen_rtx_IF_THEN_ELSE (mode, cond, |
| operands[2], const0_rtx))); |
| |
| /* Flip the test for the second operand. */ |
| cond = gen_rtx_fmt_ee ((code == EQ) ? NE : EQ, GET_MODE (op0), op0, op1); |
| |
| emit_insn (gen_rtx_SET (temp2, |
| gen_rtx_IF_THEN_ELSE (mode, cond, |
| operands[3], const0_rtx))); |
| |
| /* Merge the two results, at least one is guaranteed to be zero. */ |
| emit_insn (gen_rtx_SET (operands[0], gen_rtx_IOR (mode, temp, temp2))); |
| } |
| else |
| emit_insn (gen_rtx_SET (operands[0], |
| gen_rtx_IF_THEN_ELSE (GET_MODE (operands[0]), cond, |
| operands[2], operands[3]))); |
| } |
| |
| /* Implement TARGET_EXPAND_BUILTIN_VA_START. */ |
| |
| static void |
| loongarch_va_start (tree valist, rtx nextarg) |
| { |
| nextarg = plus_constant (Pmode, nextarg, -cfun->machine->varargs_size); |
| std_expand_builtin_va_start (valist, nextarg); |
| } |
| |
| /* Implement TARGET_FUNCTION_OK_FOR_SIBCALL. */ |
| |
| static bool |
| loongarch_function_ok_for_sibcall (tree decl ATTRIBUTE_UNUSED, |
| tree exp ATTRIBUTE_UNUSED) |
| { |
| /* Always OK. */ |
| return true; |
| } |
| |
| /* Emit straight-line code to move LENGTH bytes from SRC to DEST. |
| Assume that the areas do not overlap. */ |
| |
| static void |
| loongarch_block_move_straight (rtx dest, rtx src, HOST_WIDE_INT length) |
| { |
| HOST_WIDE_INT offset, delta; |
| unsigned HOST_WIDE_INT bits; |
| int i; |
| machine_mode mode; |
| rtx *regs; |
| |
| bits = MIN (BITS_PER_WORD, MIN (MEM_ALIGN (src), MEM_ALIGN (dest))); |
| |
| mode = int_mode_for_size (bits, 0).require (); |
| delta = bits / BITS_PER_UNIT; |
| |
| /* Allocate a buffer for the temporary registers. */ |
| regs = XALLOCAVEC (rtx, length / delta); |
| |
| /* Load as many BITS-sized chunks as possible. Use a normal load if |
| the source has enough alignment, otherwise use left/right pairs. */ |
| for (offset = 0, i = 0; offset + delta <= length; offset += delta, i++) |
| { |
| regs[i] = gen_reg_rtx (mode); |
| loongarch_emit_move (regs[i], adjust_address (src, mode, offset)); |
| } |
| |
| for (offset = 0, i = 0; offset + delta <= length; offset += delta, i++) |
| loongarch_emit_move (adjust_address (dest, mode, offset), regs[i]); |
| |
| /* Mop up any left-over bytes. */ |
| if (offset < length) |
| { |
| src = adjust_address (src, BLKmode, offset); |
| dest = adjust_address (dest, BLKmode, offset); |
| move_by_pieces (dest, src, length - offset, |
| MIN (MEM_ALIGN (src), MEM_ALIGN (dest)), |
| (enum memop_ret) 0); |
| } |
| } |
| |
| /* Helper function for doing a loop-based block operation on memory |
| reference MEM. Each iteration of the loop will operate on LENGTH |
| bytes of MEM. |
| |
| Create a new base register for use within the loop and point it to |
| the start of MEM. Create a new memory reference that uses this |
| register. Store them in *LOOP_REG and *LOOP_MEM respectively. */ |
| |
| static void |
| loongarch_adjust_block_mem (rtx mem, HOST_WIDE_INT length, rtx *loop_reg, |
| rtx *loop_mem) |
| { |
| *loop_reg = copy_addr_to_reg (XEXP (mem, 0)); |
| |
| /* Although the new mem does not refer to a known location, |
| it does keep up to LENGTH bytes of alignment. */ |
| *loop_mem = change_address (mem, BLKmode, *loop_reg); |
| set_mem_align (*loop_mem, MIN (MEM_ALIGN (mem), length * BITS_PER_UNIT)); |
| } |
| |
| /* Move LENGTH bytes from SRC to DEST using a loop that moves BYTES_PER_ITER |
| bytes at a time. LENGTH must be at least BYTES_PER_ITER. Assume that |
| the memory regions do not overlap. */ |
| |
| static void |
| loongarch_block_move_loop (rtx dest, rtx src, HOST_WIDE_INT length, |
| HOST_WIDE_INT bytes_per_iter) |
| { |
| rtx_code_label *label; |
| rtx src_reg, dest_reg, final_src, test; |
| HOST_WIDE_INT leftover; |
| |
| leftover = length % bytes_per_iter; |
| length -= leftover; |
| |
| /* Create registers and memory references for use within the loop. */ |
| loongarch_adjust_block_mem (src, bytes_per_iter, &src_reg, &src); |
| loongarch_adjust_block_mem (dest, bytes_per_iter, &dest_reg, &dest); |
| |
| /* Calculate the value that SRC_REG should have after the last iteration |
| of the loop. */ |
| final_src = expand_simple_binop (Pmode, PLUS, src_reg, GEN_INT (length), 0, |
| 0, OPTAB_WIDEN); |
| |
| /* Emit the start of the loop. */ |
| label = gen_label_rtx (); |
| emit_label (label); |
| |
| /* Emit the loop body. */ |
| loongarch_block_move_straight (dest, src, bytes_per_iter); |
| |
| /* Move on to the next block. */ |
| loongarch_emit_move (src_reg, |
| plus_constant (Pmode, src_reg, bytes_per_iter)); |
| loongarch_emit_move (dest_reg, |
| plus_constant (Pmode, dest_reg, bytes_per_iter)); |
| |
| /* Emit the loop condition. */ |
| test = gen_rtx_NE (VOIDmode, src_reg, final_src); |
| if (Pmode == DImode) |
| emit_jump_insn (gen_cbranchdi4 (test, src_reg, final_src, label)); |
| else |
| emit_jump_insn (gen_cbranchsi4 (test, src_reg, final_src, label)); |
| |
| /* Mop up any left-over bytes. */ |
| if (leftover) |
| loongarch_block_move_straight (dest, src, leftover); |
| else |
| /* Temporary fix for PR79150. */ |
| emit_insn (gen_nop ()); |
| } |
| |
| /* Expand a cpymemsi instruction, which copies LENGTH bytes from |
| memory reference SRC to memory reference DEST. */ |
| |
| bool |
| loongarch_expand_block_move (rtx dest, rtx src, rtx length) |
| { |
| int max_move_bytes = LARCH_MAX_MOVE_BYTES_STRAIGHT; |
| |
| if (CONST_INT_P (length) |
| && INTVAL (length) <= loongarch_max_inline_memcpy_size) |
| { |
| if (INTVAL (length) <= max_move_bytes) |
| { |
| loongarch_block_move_straight (dest, src, INTVAL (length)); |
| return true; |
| } |
| else if (optimize) |
| { |
| loongarch_block_move_loop (dest, src, INTVAL (length), |
| LARCH_MAX_MOVE_BYTES_PER_LOOP_ITER); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* Return true if loongarch_expand_block_move is the preferred |
| implementation of the 'cpymemsi' template. */ |
| |
| bool |
| loongarch_do_optimize_block_move_p (void) |
| { |
| /* if -m[no-]memcpy is given explicitly. */ |
| if (target_flags_explicit & MASK_MEMCPY) |
| return !TARGET_MEMCPY; |
| |
| /* if not, don't optimize under -Os. */ |
| return !optimize_size; |
| } |
| |
| /* Expand a QI or HI mode atomic memory operation. |
| |
| GENERATOR contains a pointer to the gen_* function that generates |
| the SI mode underlying atomic operation using masks that we |
| calculate. |
| |
| RESULT is the return register for the operation. Its value is NULL |
| if unused. |
| |
| MEM is the location of the atomic access. |
| |
| OLDVAL is the first operand for the operation. |
| |
| NEWVAL is the optional second operand for the operation. Its value |
| is NULL if unused. */ |
| |
| void |
| loongarch_expand_atomic_qihi (union loongarch_gen_fn_ptrs generator, |
| rtx result, rtx mem, rtx oldval, rtx newval, |
| rtx model) |
| { |
| rtx orig_addr, memsi_addr, memsi, shift, shiftsi, unshifted_mask; |
| rtx unshifted_mask_reg, mask, inverted_mask, si_op; |
| rtx res = NULL; |
| machine_mode mode; |
| |
| mode = GET_MODE (mem); |
| |
| /* Compute the address of the containing SImode value. */ |
| orig_addr = force_reg (Pmode, XEXP (mem, 0)); |
| memsi_addr = loongarch_force_binary (Pmode, AND, orig_addr, |
| force_reg (Pmode, GEN_INT (-4))); |
| |
| /* Create a memory reference for it. */ |
| memsi = gen_rtx_MEM (SImode, memsi_addr); |
| set_mem_alias_set (memsi, ALIAS_SET_MEMORY_BARRIER); |
| MEM_VOLATILE_P (memsi) = MEM_VOLATILE_P (mem); |
| |
| /* Work out the byte offset of the QImode or HImode value, |
| counting from the least significant byte. */ |
| shift = loongarch_force_binary (Pmode, AND, orig_addr, GEN_INT (3)); |
| /* Multiply by eight to convert the shift value from bytes to bits. */ |
| loongarch_emit_binary (ASHIFT, shift, shift, GEN_INT (3)); |
| |
| /* Make the final shift an SImode value, so that it can be used in |
| SImode operations. */ |
| shiftsi = force_reg (SImode, gen_lowpart (SImode, shift)); |
| |
| /* Set MASK to an inclusive mask of the QImode or HImode value. */ |
| unshifted_mask = GEN_INT (GET_MODE_MASK (mode)); |
| unshifted_mask_reg = force_reg (SImode, unshifted_mask); |
| mask = loongarch_force_binary (SImode, ASHIFT, unshifted_mask_reg, shiftsi); |
| |
| /* Compute the equivalent exclusive mask. */ |
| inverted_mask = gen_reg_rtx (SImode); |
| emit_insn (gen_rtx_SET (inverted_mask, gen_rtx_NOT (SImode, mask))); |
| |
| /* Shift the old value into place. */ |
| if (oldval != const0_rtx) |
| { |
| oldval = convert_modes (SImode, mode, oldval, true); |
| oldval = force_reg (SImode, oldval); |
| oldval = loongarch_force_binary (SImode, ASHIFT, oldval, shiftsi); |
| } |
| |
| /* Do the same for the new value. */ |
| if (newval && newval != const0_rtx) |
| { |
| newval = convert_modes (SImode, mode, newval, true); |
| newval = force_reg (SImode, newval); |
| newval = loongarch_force_binary (SImode, ASHIFT, newval, shiftsi); |
| } |
| |
| /* Do the SImode atomic access. */ |
| if (result) |
| res = gen_reg_rtx (SImode); |
| |
| if (newval) |
| si_op = generator.fn_7 (res, memsi, mask, inverted_mask, oldval, newval, |
| model); |
| else if (result) |
| si_op = generator.fn_6 (res, memsi, mask, inverted_mask, oldval, model); |
| else |
| si_op = generator.fn_5 (memsi, mask, inverted_mask, oldval, model); |
| |
| emit_insn (si_op); |
| |
| if (result) |
| { |
| /* Shift and convert the result. */ |
| loongarch_emit_binary (AND, res, res, mask); |
| loongarch_emit_binary (LSHIFTRT, res, res, shiftsi); |
| loongarch_emit_move (result, gen_lowpart (GET_MODE (result), res)); |
| } |
| } |
| |
| /* Return true if (zero_extract OP WIDTH BITPOS) can be used as the |
| source of an "ext" instruction or the destination of an "ins" |
| instruction. OP must be a register operand and the following |
| conditions must hold: |
| |
| 0 <= BITPOS < GET_MODE_BITSIZE (GET_MODE (op)) |
| 0 < WIDTH <= GET_MODE_BITSIZE (GET_MODE (op)) |
| 0 < BITPOS + WIDTH <= GET_MODE_BITSIZE (GET_MODE (op)) |
| |
| Also reject lengths equal to a word as they are better handled |
| by the move patterns. */ |
| |
| bool |
| loongarch_use_ins_ext_p (rtx op, HOST_WIDE_INT width, HOST_WIDE_INT bitpos) |
| { |
| if (!register_operand (op, VOIDmode) |
| || GET_MODE_BITSIZE (GET_MODE (op)) > BITS_PER_WORD) |
| return false; |
| |
| if (!IN_RANGE (width, 1, GET_MODE_BITSIZE (GET_MODE (op)) - 1)) |
| return false; |
| |
| if (bitpos < 0 || bitpos + width > GET_MODE_BITSIZE (GET_MODE (op))) |
| return false; |
| |
| return true; |
| } |
| |
| /* Print the text for PRINT_OPERAND punctation character CH to FILE. |
| The punctuation characters are: |
| |
| '.' Print the name of the register with a hard-wired zero (zero or $r0). |
| '$' Print the name of the stack pointer register (sp or $r3). |
| |
| See also loongarch_init_print_operand_punct. */ |
| |
| static void |
| loongarch_print_operand_punctuation (FILE *file, int ch) |
| { |
| switch (ch) |
| { |
| case '.': |
| fputs (reg_names[GP_REG_FIRST + 0], file); |
| break; |
| |
| case '$': |
| fputs (reg_names[STACK_POINTER_REGNUM], file); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| break; |
| } |
| } |
| |
| /* Initialize loongarch_print_operand_punct. */ |
| |
| static void |
| loongarch_init_print_operand_punct (void) |
| { |
| const char *p; |
| |
| for (p = ".$"; *p; p++) |
| loongarch_print_operand_punct[(unsigned char) *p] = true; |
| } |
| |
| /* PRINT_OPERAND prefix LETTER refers to the integer branch instruction |
| associated with condition CODE. Print the condition part of the |
| opcode to FILE. */ |
| |
| static void |
| loongarch_print_int_branch_condition (FILE *file, enum rtx_code code, |
| int letter) |
| { |
| switch (code) |
| { |
| case EQ: |
| case NE: |
| case GT: |
| case GE: |
| case LT: |
| case LE: |
| case GTU: |
| case GEU: |
| case LTU: |
| case LEU: |
| /* Conveniently, the LoongArch names for these conditions are the same |
| as their RTL equivalents. */ |
| fputs (GET_RTX_NAME (code), file); |
| break; |
| |
| default: |
| output_operand_lossage ("'%%%c' is not a valid operand prefix", letter); |
| break; |
| } |
| } |
| |
| /* Likewise floating-point branches. */ |
| |
| static void |
| loongarch_print_float_branch_condition (FILE *file, enum rtx_code code, |
| int letter) |
| { |
| switch (code) |
| { |
| case EQ: |
| fputs ("ceqz", file); |
| break; |
| |
| case NE: |
| fputs ("cnez", file); |
| break; |
| |
| default: |
| output_operand_lossage ("'%%%c' is not a valid operand prefix", letter); |
| break; |
| } |
| } |
| |
| /* Implement TARGET_PRINT_OPERAND_PUNCT_VALID_P. */ |
| |
| static bool |
| loongarch_print_operand_punct_valid_p (unsigned char code) |
| { |
| return loongarch_print_operand_punct[code]; |
| } |
| |
| /* Return true if a FENCE should be emitted to before a memory access to |
| implement the release portion of memory model MODEL. */ |
| |
| static bool |
| loongarch_memmodel_needs_rel_acq_fence (enum memmodel model) |
| { |
| switch (model) |
| { |
| case MEMMODEL_ACQ_REL: |
| case MEMMODEL_SEQ_CST: |
| case MEMMODEL_SYNC_SEQ_CST: |
| case MEMMODEL_RELEASE: |
| case MEMMODEL_SYNC_RELEASE: |
| case MEMMODEL_ACQUIRE: |
| case MEMMODEL_CONSUME: |
| case MEMMODEL_SYNC_ACQUIRE: |
| return true; |
| |
| case MEMMODEL_RELAXED: |
| return false; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Return true if a FENCE should be emitted to before a memory access to |
| implement the release portion of memory model MODEL. */ |
| |
| static bool |
| loongarch_memmodel_needs_release_fence (enum memmodel model) |
| { |
| switch (model) |
| { |
| case MEMMODEL_ACQ_REL: |
| case MEMMODEL_SEQ_CST: |
| case MEMMODEL_SYNC_SEQ_CST: |
| case MEMMODEL_RELEASE: |
| case MEMMODEL_SYNC_RELEASE: |
| return true; |
| |
| case MEMMODEL_ACQUIRE: |
| case MEMMODEL_CONSUME: |
| case MEMMODEL_SYNC_ACQUIRE: |
| case MEMMODEL_RELAXED: |
| return false; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Print symbolic operand OP, which is part of a HIGH or LO_SUM |
| in context CONTEXT. HI_RELOC indicates a high-part reloc. */ |
| |
| static void |
| loongarch_print_operand_reloc (FILE *file, rtx op, bool hi64_part, |
| bool hi_reloc) |
| { |
| const char *reloc; |
| enum loongarch_symbol_type symbol_type = |
| loongarch_classify_symbolic_expression (op); |
| |
| if (loongarch_symbol_extreme_p (symbol_type)) |
| gcc_assert (TARGET_EXPLICIT_RELOCS); |
| |
| switch (symbol_type) |
| { |
| case SYMBOL_PCREL64: |
| if (hi64_part) |
| { |
| reloc = hi_reloc ? "%pc64_hi12" : "%pc64_lo20"; |
| break; |
| } |
| /* fall through */ |
| case SYMBOL_PCREL: |
| reloc = hi_reloc ? "%pc_hi20" : "%pc_lo12"; |
| break; |
| |
| case SYMBOL_GOT_DISP: |
| if (hi64_part) |
| { |
| if (TARGET_CMODEL_EXTREME) |
| reloc = hi_reloc ? "%got64_pc_hi12" : "%got64_pc_lo20"; |
| else |
| gcc_unreachable (); |
| } |
| else |
| reloc = hi_reloc ? "%got_pc_hi20" : "%got_pc_lo12"; |
| break; |
| |
| case SYMBOL_TLS_IE: |
| if (hi64_part) |
| { |
| if (TARGET_CMODEL_EXTREME) |
| reloc = hi_reloc ? "%ie64_pc_hi12" : "%ie64_pc_lo20"; |
| else |
| gcc_unreachable (); |
| } |
| else |
| reloc = hi_reloc ? "%ie_pc_hi20" : "%ie_pc_lo12"; |
| break; |
| |
| case SYMBOL_TLS_LE: |
| if (hi64_part) |
| { |
| if (TARGET_CMODEL_EXTREME) |
| reloc = hi_reloc ? "%le64_hi12" : "%le64_lo20"; |
| else |
| gcc_unreachable (); |
| } |
| else |
| reloc = hi_reloc ? "%le_hi20" : "%le_lo12"; |
| break; |
| |
| case SYMBOL_TLSGD: |
| if (hi64_part) |
| { |
| if (TARGET_CMODEL_EXTREME) |
| reloc = hi_reloc ? "%got64_pc_hi12" : "%got64_pc_lo20"; |
| else |
| gcc_unreachable (); |
| } |
| else |
| reloc = hi_reloc ? "%gd_pc_hi20" : "%got_pc_lo12"; |
| break; |
| |
| case SYMBOL_TLSLDM: |
| if (hi64_part) |
| { |
| if (TARGET_CMODEL_EXTREME) |
| reloc = hi_reloc ? "%got64_pc_hi12" : "%got64_pc_lo20"; |
| else |
| gcc_unreachable (); |
| } |
| else |
| reloc = hi_reloc ? "%ld_pc_hi20" : "%got_pc_lo12"; |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| fprintf (file, "%s(", reloc); |
| output_addr_const (file, loongarch_strip_unspec_address (op)); |
| fputc (')', file); |
| } |
| |
| /* Implement TARGET_PRINT_OPERAND. The LoongArch-specific operand codes are: |
| |
| 'A' Print a _DB suffix if the memory model requires a release. |
| 'b' Print the address of a memory operand, without offset. |
| 'C' Print the integer branch condition for comparison OP. |
| 'd' Print CONST_INT OP in decimal. |
| 'F' Print the FPU branch condition for comparison OP. |
| 'G' Print a DBAR insn if the memory model requires a release. |
| 'H' Print address 52-61bit relocation associated with OP. |
| 'h' Print the high-part relocation associated with OP. |
| 'i' Print i if the operand is not a register. |
| 'L' Print the low-part relocation associated with OP. |
| 'm' Print one less than CONST_INT OP in decimal. |
| 'N' Print the inverse of the integer branch condition for comparison OP. |
| 'r' Print address 12-31bit relocation associated with OP. |
| 'R' Print address 32-51bit relocation associated with OP. |
| 'T' Print 'f' for (eq:CC ...), 't' for (ne:CC ...), |
| 'z' for (eq:?I ...), 'n' for (ne:?I ...). |
| 't' Like 'T', but with the EQ/NE cases reversed |
| 'V' Print exact log2 of CONST_INT OP element 0 of a replicated |
| CONST_VECTOR in decimal. |
| 'W' Print the inverse of the FPU branch condition for comparison OP. |
| 'X' Print CONST_INT OP in hexadecimal format. |
| 'x' Print the low 16 bits of CONST_INT OP in hexadecimal format. |
| 'Y' Print loongarch_fp_conditions[INTVAL (OP)] |
| 'y' Print exact log2 of CONST_INT OP in decimal. |
| 'Z' Print OP and a comma for 8CC, otherwise print nothing. |
| 'z' Print $0 if OP is zero, otherwise print OP normally. */ |
| |
| static void |
| loongarch_print_operand (FILE *file, rtx op, int letter) |
| { |
| enum rtx_code code; |
| |
| if (loongarch_print_operand_punct_valid_p (letter)) |
| { |
| loongarch_print_operand_punctuation (file, letter); |
| return; |
| } |
| |
| gcc_assert (op); |
| code = GET_CODE (op); |
| |
| switch (letter) |
| { |
| case 'A': |
| if (loongarch_memmodel_needs_rel_acq_fence ((enum memmodel) INTVAL (op))) |
| fputs ("_db", file); |
| break; |
| |
| case 'C': |
| loongarch_print_int_branch_condition (file, code, letter); |
| break; |
| |
| case 'd': |
| if (CONST_INT_P (op)) |
| fprintf (file, HOST_WIDE_INT_PRINT_DEC, INTVAL (op)); |
| else |
| output_operand_lossage ("invalid use of '%%%c'", letter); |
| break; |
| |
| case 'F': |
| loongarch_print_float_branch_condition (file, code, letter); |
| break; |
| |
| case 'G': |
| if (loongarch_memmodel_needs_release_fence ((enum memmodel) INTVAL (op))) |
| fputs ("dbar\t0", file); |
| break; |
| |
| case 'h': |
| if (code == HIGH) |
| op = XEXP (op, 0); |
| loongarch_print_operand_reloc (file, op, false /* hi64_part */, |
| true /* hi_reloc */); |
| break; |
| |
| case 'H': |
| loongarch_print_operand_reloc (file, op, true /* hi64_part */, |
| true /* hi_reloc */); |
| break; |
| |
| case 'i': |
| if (code != REG) |
| fputs ("i", file); |
| break; |
| |
| case 'L': |
| loongarch_print_operand_reloc (file, op, false /* hi64_part*/, |
| false /* lo_reloc */); |
| break; |
| |
| case 'm': |
| if (CONST_INT_P (op)) |
| fprintf (file, HOST_WIDE_INT_PRINT_DEC, INTVAL (op) - 1); |
| else |
| output_operand_lossage ("invalid use of '%%%c'", letter); |
| break; |
| |
| case 'N': |
| loongarch_print_int_branch_condition (file, reverse_condition (code), |
| letter); |
| break; |
| |
| case 'r': |
| loongarch_print_operand_reloc (file, op, false /* hi64_part */, |
| true /* lo_reloc */); |
| break; |
| |
| case 'R': |
| loongarch_print_operand_reloc (file, op, true /* hi64_part */, |
| false /* lo_reloc */); |
| break; |
| |
| case 't': |
| case 'T': |
| { |
| int truth = (code == NE) == (letter == 'T'); |
| fputc ("zfnt"[truth * 2 + FCC_REG_P (REGNO (XEXP (op, 0)))], file); |
| } |
| break; |
| |
| case 'V': |
| if (CONST_VECTOR_P (op)) |
| { |
| machine_mode mode = GET_MODE_INNER (GET_MODE (op)); |
| unsigned HOST_WIDE_INT val = UINTVAL (CONST_VECTOR_ELT (op, 0)); |
| int vlog2 = exact_log2 (val & GET_MODE_MASK (mode)); |
| if (vlog2 != -1) |
| fprintf (file, "%d", vlog2); |
| else |
| output_operand_lossage ("invalid use of '%%%c'", letter); |
| } |
| else |
| output_operand_lossage ("invalid use of '%%%c'", letter); |
| break; |
| |
| case 'W': |
| loongarch_print_float_branch_condition (file, reverse_condition (code), |
| letter); |
| break; |
| |
| case 'x': |
| if (CONST_INT_P (op)) |
| fprintf (file, HOST_WIDE_INT_PRINT_HEX, INTVAL (op) & 0xffff); |
| else |
| output_operand_lossage ("invalid use of '%%%c'", letter); |
| break; |
| |
| case 'X': |
| if (CONST_INT_P (op)) |
| fprintf (file, HOST_WIDE_INT_PRINT_HEX, INTVAL (op)); |
| else |
| output_operand_lossage ("invalid use of '%%%c'", letter); |
| break; |
| |
| case 'y': |
| if (CONST_INT_P (op)) |
| { |
| int val = exact_log2 (INTVAL (op)); |
| if (val != -1) |
| fprintf (file, "%d", val); |
| else |
| output_operand_lossage ("invalid use of '%%%c'", letter); |
| } |
| else |
| output_operand_lossage ("invalid use of '%%%c'", letter); |
| break; |
| |
| case 'Y': |
| if (code == CONST_INT |
| && UINTVAL (op) < ARRAY_SIZE (loongarch_fp_conditions)) |
| fputs (loongarch_fp_conditions[UINTVAL (op)], file); |
| else |
| output_operand_lossage ("'%%%c' is not a valid operand prefix", |
| letter); |
| break; |
| |
| case 'Z': |
| loongarch_print_operand (file, op, 0); |
| fputc (',', file); |
| break; |
| |
| default: |
| switch (code) |
| { |
| case REG: |
| { |
| unsigned int regno = REGNO (op); |
| if (letter && letter != 'z') |
| output_operand_lossage ("invalid use of '%%%c'", letter); |
| fprintf (file, "%s", reg_names[regno]); |
| } |
| break; |
| |
| case MEM: |
| if (letter == 'D') |
| output_address (GET_MODE (op), |
| plus_constant (Pmode, XEXP (op, 0), 4)); |
| else if (letter == 'b') |
| { |
| gcc_assert (REG_P (XEXP (op, 0))); |
| loongarch_print_operand (file, XEXP (op, 0), 0); |
| } |
| else if (letter && letter != 'z') |
| output_operand_lossage ("invalid use of '%%%c'", letter); |
| else |
| output_address (GET_MODE (op), XEXP (op, 0)); |
| break; |
| |
| default: |
| if (letter == 'z' && op == CONST0_RTX (GET_MODE (op))) |
| fputs (reg_names[GP_REG_FIRST], file); |
| else if (letter && letter != 'z') |
| output_operand_lossage ("invalid use of '%%%c'", letter); |
| else |
| output_addr_const (file, loongarch_strip_unspec_address (op)); |
| break; |
| } |
| } |
| } |
| |
| /* Implement TARGET_PRINT_OPERAND_ADDRESS. */ |
| |
| static void |
| loongarch_print_operand_address (FILE *file, machine_mode /* mode */, rtx x) |
| { |
| struct loongarch_address_info addr; |
| |
| if (loongarch_classify_address (&addr, x, word_mode, true)) |
| switch (addr.type) |
| { |
| case ADDRESS_REG: |
| fprintf (file, "%s,", reg_names[REGNO (addr.reg)]); |
| loongarch_print_operand (file, addr.offset, 0); |
| return; |
| |
| case ADDRESS_REG_REG: |
| fprintf (file, "%s,%s", reg_names[REGNO (addr.reg)], |
| reg_names[REGNO (addr.offset)]); |
| return; |
| |
| case ADDRESS_LO_SUM: |
| fprintf (file, "%s,", reg_names[REGNO (addr.reg)]); |
| loongarch_print_operand_reloc (file, addr.offset, false /* hi64_part */, |
| false /* hi_reloc */); |
| return; |
| |
| case ADDRESS_CONST_INT: |
| fprintf (file, "%s,", reg_names[GP_REG_FIRST]); |
| output_addr_const (file, x); |
| return; |
| |
| case ADDRESS_SYMBOLIC: |
| output_addr_const (file, loongarch_strip_unspec_address (x)); |
| return; |
| } |
| if (CONST_INT_P (x)) |
| output_addr_const (file, x); |
| else |
| gcc_unreachable (); |
| } |
| |
| /* Implement TARGET_ASM_SELECT_RTX_SECTION. */ |
| |
| static section * |
| loongarch_select_rtx_section (machine_mode mode, rtx x, |
| unsigned HOST_WIDE_INT align) |
| { |
| /* ??? Consider using mergeable small data sections. */ |
| if (loongarch_rtx_constant_in_small_data_p (mode)) |
| return get_named_section (NULL, ".sdata", 0); |
| |
| return default_elf_select_rtx_section (mode, x, align); |
| } |
| |
| /* Implement TARGET_ASM_FUNCTION_RODATA_SECTION. |
| |
| The complication here is that jump tables will use absolute addresses, |
| and should therefore not be included in the read-only part of a DSO. |
| Handle such cases by selecting a normal data section instead of a |
| read-only one. The logic apes that in default_function_rodata_section. */ |
| |
| static section * |
| loongarch_function_rodata_section (tree decl, bool) |
| { |
| return default_function_rodata_section (decl, false); |
| } |
| |
| /* Implement TARGET_IN_SMALL_DATA_P. */ |
| |
| static bool |
| loongarch_in_small_data_p (const_tree decl) |
| { |
| int size; |
| |
| if (TREE_CODE (decl) == STRING_CST || TREE_CODE (decl) == FUNCTION_DECL) |
| return false; |
| |
| if (VAR_P (decl) && DECL_SECTION_NAME (decl) != 0) |
| { |
| const char *name; |
| |
| /* Reject anything that isn't in a known small-data section. */ |
| name = DECL_SECTION_NAME (decl); |
| if (strcmp (name, ".sdata") != 0 && strcmp (name, ".sbss") != 0) |
| return false; |
| |
| /* If a symbol is defined externally, the assembler will use the |
| usual -G rules when deciding how to implement macros. */ |
| if (!DECL_EXTERNAL (decl)) |
| return true; |
| } |
| |
| /* We have traditionally not treated zero-sized objects as small data, |
| so this is now effectively part of the ABI. */ |
| size = int_size_in_bytes (TREE_TYPE (decl)); |
| return size > 0 && size <= g_switch_value; |
| } |
| |
| /* The LoongArch debug format wants all automatic variables and arguments |
| to be in terms of the virtual frame pointer (stack pointer before |
| any adjustment in the function), while the LoongArch linker wants |
| the frame pointer to be the stack pointer after the initial |
| adjustment. So, we do the adjustment here. The arg pointer (which |
| is eliminated) points to the virtual frame pointer, while the frame |
| pointer (which may be eliminated) points to the stack pointer after |
| the initial adjustments. */ |
| |
| HOST_WIDE_INT |
| loongarch_debugger_offset (rtx addr, HOST_WIDE_INT offset) |
| { |
| rtx offset2 = const0_rtx; |
| rtx reg = eliminate_constant_term (addr, &offset2); |
| |
| if (offset == 0) |
| offset = INTVAL (offset2); |
| |
| if (reg == stack_pointer_rtx |
| || reg == frame_pointer_rtx |
| || reg == hard_frame_pointer_rtx) |
| { |
| offset -= cfun->machine->frame.total_size; |
| if (reg == hard_frame_pointer_rtx) |
| offset += cfun->machine->frame.hard_frame_pointer_offset; |
| } |
| |
| return offset; |
| } |
| |
| /* Implement ASM_OUTPUT_EXTERNAL. */ |
| |
| void |
| loongarch_output_external (FILE *file, tree decl, const char *name) |
| { |
| default_elf_asm_output_external (file, decl, name); |
| |
| /* We output the name if and only if TREE_SYMBOL_REFERENCED is |
| set in order to avoid putting out names that are never really |
| used. */ |
| if (TREE_SYMBOL_REFERENCED (DECL_ASSEMBLER_NAME (decl))) |
| { |
| if (loongarch_in_small_data_p (decl)) |
| { |
| /* When using assembler macros, emit .extern directives for |
| all small-data externs so that the assembler knows how |
| big they are. |
| |
| In most cases it would be safe (though pointless) to emit |
| .externs for other symbols too. One exception is when an |
| object is within the -G limit but declared by the user to |
| be in a section other than .sbss or .sdata. */ |
| fputs ("\t.extern\t", file); |
| assemble_name (file, name); |
| fprintf (file, ", " HOST_WIDE_INT_PRINT_DEC "\n", |
| int_size_in_bytes (TREE_TYPE (decl))); |
| } |
| } |
| } |
| |
| /* Implement TARGET_ASM_OUTPUT_DWARF_DTPREL. */ |
| |
| static void ATTRIBUTE_UNUSED |
| loongarch_output_dwarf_dtprel (FILE *file, int size, rtx x) |
| { |
| switch (size) |
| { |
| case 4: |
| fputs ("\t.dtprelword\t", file); |
| break; |
| |
| case 8: |
| fputs ("\t.dtpreldword\t", file); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| output_addr_const (file, x); |
| fputs ("+0x8000", file); |
| } |
| |
| /* Implement ASM_OUTPUT_ASCII. */ |
| |
| void |
| loongarch_output_ascii (FILE *stream, const char *string, size_t len) |
| { |
| size_t i; |
| int cur_pos; |
| |
| cur_pos = 17; |
| fprintf (stream, "\t.ascii\t\""); |
| for (i = 0; i < len; i++) |
| { |
| int c; |
| |
| c = (unsigned char) string[i]; |
| if (ISPRINT (c)) |
| { |
| if (c == '\\' || c == '\"') |
| { |
| putc ('\\', stream); |
| cur_pos++; |
| } |
| putc (c, stream); |
| cur_pos++; |
| } |
| else |
| { |
| fprintf (stream, "\\%03o", c); |
| cur_pos += 4; |
| } |
| |
| if (cur_pos > 72 && i + 1 < len) |
| { |
| cur_pos = 17; |
| fprintf (stream, "\"\n\t.ascii\t\""); |
| } |
| } |
| fprintf (stream, "\"\n"); |
| } |
| |
| /* Implement TARGET_FRAME_POINTER_REQUIRED. */ |
| |
| static bool |
| loongarch_frame_pointer_required (void) |
| { |
| /* If the function contains dynamic stack allocations, we need to |
| use the frame pointer to access the static parts of the frame. */ |
| if (cfun->calls_alloca) |
| return true; |
| |
| return false; |
| } |
| |
| /* Implement TARGET_CAN_ELIMINATE. Make sure that we're not trying |
| to eliminate to the wrong hard frame pointer. */ |
| |
| static bool |
| loongarch_can_eliminate (const int from ATTRIBUTE_UNUSED, const int to) |
| { |
| return (to == HARD_FRAME_POINTER_REGNUM || to == STACK_POINTER_REGNUM); |
| } |
| |
| /* Implement RETURN_ADDR_RTX. We do not support moving back to a |
| previous frame. */ |
| |
| rtx |
| loongarch_return_addr (int count, rtx frame ATTRIBUTE_UNUSED) |
| { |
| if (count != 0) |
| return const0_rtx; |
| |
| return get_hard_reg_initial_val (Pmode, RETURN_ADDR_REGNUM); |
| } |
| |
| /* Emit code to change the current function's return address to |
| ADDRESS. SCRATCH is available as a scratch register, if needed. |
| ADDRESS and SCRATCH are both word-mode GPRs. */ |
| |
| void |
| loongarch_set_return_address (rtx address, rtx scratch) |
| { |
| rtx slot_address; |
| |
| gcc_assert (BITSET_P (cfun->machine->frame.mask, RETURN_ADDR_REGNUM)); |
| |
| if (frame_pointer_needed) |
| slot_address = loongarch_add_offset (scratch, hard_frame_pointer_rtx, |
| -UNITS_PER_WORD); |
| else |
| slot_address = loongarch_add_offset (scratch, stack_pointer_rtx, |
| cfun->machine->frame.gp_sp_offset); |
| |
| loongarch_emit_move (gen_frame_mem (GET_MODE (address), slot_address), |
| address); |
| } |
| |
| /* Return true if register REGNO can store a value of mode MODE. |
| The result of this function is cached in loongarch_hard_regno_mode_ok. */ |
| |
| static bool |
| loongarch_hard_regno_mode_ok_uncached (unsigned int regno, machine_mode mode) |
| { |
| unsigned int size; |
| enum mode_class mclass; |
| |
| if (mode == FCCmode) |
| return FCC_REG_P (regno); |
| |
| size = GET_MODE_SIZE (mode); |
| mclass = GET_MODE_CLASS (mode); |
| |
| if (GP_REG_P (regno)) |
| return ((regno - GP_REG_FIRST) & 1) == 0 || size <= UNITS_PER_WORD; |
| |
| if (FP_REG_P (regno)) |
| { |
| if (mclass == MODE_FLOAT |
| || mclass == MODE_COMPLEX_FLOAT |
| || mclass == MODE_VECTOR_FLOAT) |
| return size <= UNITS_PER_FPVALUE; |
| |
| /* Allow integer modes that fit into a single register. We need |
| to put integers into FPRs when using instructions like CVT |
| and TRUNC. There's no point allowing sizes smaller than a word, |
| because the FPU has no appropriate load/store instructions. */ |
| if (mclass == MODE_INT) |
| return size >= MIN_UNITS_PER_WORD && size <= UNITS_PER_FPREG; |
| } |
| |
| return false; |
| } |
| |
| /* Implement TARGET_HARD_REGNO_MODE_OK. */ |
| |
| static bool |
| loongarch_hard_regno_mode_ok (unsigned int regno, machine_mode mode) |
| { |
| return loongarch_hard_regno_mode_ok_p[mode][regno]; |
| } |
| |
| /* Implement TARGET_HARD_REGNO_NREGS. */ |
| |
| static unsigned int |
| loongarch_hard_regno_nregs (unsigned int regno, machine_mode mode) |
| { |
| if (FCC_REG_P (regno)) |
| /* The size of FP status registers is always 4, because they only hold |
| FCCmode values, and FCCmode is always considered to be 4 bytes wide. */ |
| return (GET_MODE_SIZE (mode) + 3) / 4; |
| |
| if (FP_REG_P (regno)) |
| return (GET_MODE_SIZE (mode) + UNITS_PER_FPREG - 1) / UNITS_PER_FPREG; |
| |
| /* All other registers are word-sized. */ |
| return (GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD; |
| } |
| |
| /* Implement CLASS_MAX_NREGS, taking the maximum of the cases |
| in loongarch_hard_regno_nregs. */ |
| |
| int |
| loongarch_class_max_nregs (enum reg_class rclass, machine_mode mode) |
| { |
| int size; |
| HARD_REG_SET left; |
| |
| size = 0x8000; |
| left = reg_class_contents[rclass]; |
| if (hard_reg_set_intersect_p (left, reg_class_contents[(int) FCC_REGS])) |
| { |
| if (loongarch_hard_regno_mode_ok (FCC_REG_FIRST, mode)) |
| size = MIN (size, 4); |
| |
| left &= ~reg_class_contents[FCC_REGS]; |
| } |
| if (hard_reg_set_intersect_p (left, reg_class_contents[(int) FP_REGS])) |
| { |
| if (loongarch_hard_regno_mode_ok (FP_REG_FIRST, mode)) |
| size = MIN (size, UNITS_PER_FPREG); |
| |
| left &= ~reg_class_contents[FP_REGS]; |
| } |
| if (!hard_reg_set_empty_p (left)) |
| size = MIN (size, UNITS_PER_WORD); |
| return (GET_MODE_SIZE (mode) + size - 1) / size; |
| } |
| |
| /* Implement TARGET_CAN_CHANGE_MODE_CLASS. */ |
| |
| static bool |
| loongarch_can_change_mode_class (machine_mode, machine_mode, |
| reg_class_t rclass) |
| { |
| return !reg_classes_intersect_p (FP_REGS, rclass); |
| } |
| |
| /* Return true if moves in mode MODE can use the FPU's fmov.fmt instruction, |
| */ |
| |
| static bool |
| loongarch_mode_ok_for_mov_fmt_p (machine_mode mode) |
| { |
| switch (mode) |
| { |
| case E_FCCmode: |
| case E_SFmode: |
| return TARGET_HARD_FLOAT; |
| |
| case E_DFmode: |
| return TARGET_HARD_FLOAT && TARGET_DOUBLE_FLOAT; |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* Implement TARGET_MODES_TIEABLE_P. */ |
| |
| static bool |
| loongarch_modes_tieable_p (machine_mode mode1, machine_mode mode2) |
| { |
| /* FPRs allow no mode punning, so it's not worth tying modes if we'd |
| prefer to put one of them in FPRs. */ |
| return (mode1 == mode2 |
| || (!loongarch_mode_ok_for_mov_fmt_p (mode1) |
| && !loongarch_mode_ok_for_mov_fmt_p (mode2))); |
| } |
| |
| /* Implement TARGET_PREFERRED_RELOAD_CLASS. */ |
| |
| static reg_class_t |
| loongarch_preferred_reload_class (rtx x, reg_class_t rclass) |
| { |
| if (reg_class_subset_p (FP_REGS, rclass) |
| && loongarch_mode_ok_for_mov_fmt_p (GET_MODE (x))) |
| return FP_REGS; |
| |
| if (reg_class_subset_p (GR_REGS, rclass)) |
| rclass = GR_REGS; |
| |
| return rclass; |
| } |
| |
| /* RCLASS is a class involved in a REGISTER_MOVE_COST calculation. |
| Return a "canonical" class to represent it in later calculations. */ |
| |
| static reg_class_t |
| loongarch_canonicalize_move_class (reg_class_t rclass) |
| { |
| if (reg_class_subset_p (rclass, GENERAL_REGS)) |
| rclass = GENERAL_REGS; |
| |
| return rclass; |
| } |
| |
| /* Return the cost of moving a value from a register of class FROM to a GPR. |
| Return 0 for classes that are unions of other classes handled by this |
| function. */ |
| |
| static int |
| loongarch_move_to_gpr_cost (reg_class_t from) |
| { |
| switch (from) |
| { |
| case GENERAL_REGS: |
| /* MOVE macro. */ |
| return 2; |
| |
| case FP_REGS: |
| /* MOVFR2GR, etc. */ |
| return 4; |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* Return the cost of moving a value from a GPR to a register of class TO. |
| Return 0 for classes that are unions of other classes handled by this |
| function. */ |
| |
| static int |
| loongarch_move_from_gpr_cost (reg_class_t to) |
| { |
| switch (to) |
| { |
| case GENERAL_REGS: |
| /*MOVE macro. */ |
| return 2; |
| |
| case FP_REGS: |
| /* MOVGR2FR, etc. */ |
| return 4; |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* Implement TARGET_REGISTER_MOVE_COST. Return 0 for classes that are the |
| maximum of the move costs for subclasses; regclass will work out |
| the maximum for us. */ |
| |
| static int |
| loongarch_register_move_cost (machine_mode mode, reg_class_t from, |
| reg_class_t to) |
| { |
| reg_class_t dregs; |
| int cost1, cost2; |
| |
| from = loongarch_canonicalize_move_class (from); |
| to = loongarch_canonicalize_move_class (to); |
| |
| /* Handle moves that can be done without using general-purpose registers. */ |
| if (from == FP_REGS) |
| { |
| if (to == FP_REGS && loongarch_mode_ok_for_mov_fmt_p (mode)) |
| /* FMOV.FMT. */ |
| return 4; |
| } |
| |
| /* Handle cases in which only one class deviates from the ideal. */ |
| dregs = GENERAL_REGS; |
| if (from == dregs) |
| return loongarch_move_from_gpr_cost (to); |
| if (to == dregs) |
| return loongarch_move_to_gpr_cost (from); |
| |
| /* Handles cases that require a GPR temporary. */ |
| cost1 = loongarch_move_to_gpr_cost (from); |
| if (cost1 != 0) |
| { |
| cost2 = loongarch_move_from_gpr_cost (to); |
| if (cost2 != 0) |
| return cost1 + cost2; |
| } |
| |
| return 0; |
| } |
| |
| /* Implement TARGET_MEMORY_MOVE_COST. */ |
| |
| static int |
| loongarch_memory_move_cost (machine_mode mode, reg_class_t rclass, bool in) |
| { |
| return (loongarch_cost->memory_latency |
| + memory_move_secondary_cost (mode, rclass, in)); |
| } |
| |
| /* Return the register class required for a secondary register when |
| copying between one of the registers in RCLASS and value X, which |
| has mode MODE. X is the source of the move if IN_P, otherwise it |
| is the destination. Return NO_REGS if no secondary register is |
| needed. */ |
| |
| static reg_class_t |
| loongarch_secondary_reload (bool in_p ATTRIBUTE_UNUSED, rtx x, |
| reg_class_t rclass, machine_mode mode, |
| secondary_reload_info *sri ATTRIBUTE_UNUSED) |
| { |
| int regno; |
| |
| regno = true_regnum (x); |
| |
| if (reg_class_subset_p (rclass, FP_REGS)) |
| { |
| if (regno < 0 |
| || (MEM_P (x) |
| && (GET_MODE_SIZE (mode) == 4 || GET_MODE_SIZE (mode) == 8))) |
| /* In this case we can use fld.s, fst.s, fld.d or fst.d. */ |
| return NO_REGS; |
| |
| if (GP_REG_P (regno) || x == CONST0_RTX (mode)) |
| /* In this case we can use movgr2fr.s, movfr2gr.s, movgr2fr.d or |
| * movfr2gr.d. */ |
| return NO_REGS; |
| |
| if (CONSTANT_P (x) && !targetm.cannot_force_const_mem (mode, x)) |
| /* We can force the constant to memory and use fld.s |
| and fld.d. As above, we will use pairs of lwc1s if |
| ldc1 is not supported. */ |
| return NO_REGS; |
| |
| if (FP_REG_P (regno) && loongarch_mode_ok_for_mov_fmt_p (mode)) |
| /* In this case we can use fmov.{s/d}. */ |
| return NO_REGS; |
| |
| /* Otherwise, we need to reload through an integer register. */ |
| return GR_REGS; |
| } |
| if (FP_REG_P (regno)) |
| return reg_class_subset_p (rclass, GR_REGS) ? NO_REGS : GR_REGS; |
| |
| return NO_REGS; |
| } |
| |
| /* Implement TARGET_VALID_POINTER_MODE. */ |
| |
| static bool |
| loongarch_valid_pointer_mode (scalar_int_mode mode) |
| { |
| return mode == SImode || (TARGET_64BIT && mode == DImode); |
| } |
| |
| /* Implement TARGET_SCALAR_MODE_SUPPORTED_P. */ |
| |
| static bool |
| loongarch_scalar_mode_supported_p (scalar_mode mode) |
| { |
| if (ALL_FIXED_POINT_MODE_P (mode) |
| && GET_MODE_PRECISION (mode) <= 2 * BITS_PER_WORD) |
| return true; |
| |
| return default_scalar_mode_supported_p (mode); |
| } |
| |
| /* Return the assembly code for INSN, which has the operands given by |
| OPERANDS, and which branches to OPERANDS[0] if some condition is true. |
| BRANCH_IF_TRUE is the asm template that should be used if OPERANDS[0] |
| is in range of a direct branch. BRANCH_IF_FALSE is an inverted |
| version of BRANCH_IF_TRUE. */ |
| |
| const char * |
| loongarch_output_conditional_branch (rtx_insn *insn, rtx *operands, |
| const char *branch_if_true, |
| const char *branch_if_false) |
| { |
| unsigned int length; |
| rtx taken; |
| |
| gcc_assert (LABEL_P (operands[0])); |
| |
| length = get_attr_length (insn); |
| if (length <= 4) |
| { |
| return branch_if_true; |
| } |
| |
| /* Generate a reversed branch around a direct jump. */ |
| rtx_code_label *not_taken = gen_label_rtx (); |
| taken = operands[0]; |
| |
| /* Generate the reversed branch to NOT_TAKEN. */ |
| operands[0] = not_taken; |
| output_asm_insn (branch_if_false, operands); |
| |
| output_asm_insn ("b\t%0", &taken); |
| |
| /* Output NOT_TAKEN. */ |
| targetm.asm_out.internal_label (asm_out_file, "L", |
| CODE_LABEL_NUMBER (not_taken)); |
| return ""; |
| } |
| |
| /* Return the assembly code for INSN, which branches to OPERANDS[0] |
| if some equality condition is true. The condition is given by |
| OPERANDS[1] if !INVERTED_P, otherwise it is the inverse of |
| OPERANDS[1]. OPERANDS[2] is the comparison's first operand; |
| OPERANDS[3] is the second operand and may be zero or a register. */ |
| |
| const char * |
| loongarch_output_equal_conditional_branch (rtx_insn *insn, rtx *operands, |
| bool inverted_p) |
| { |
| const char *branch[2]; |
| if (operands[3] == const0_rtx) |
| { |
| branch[!inverted_p] = LARCH_BRANCH ("b%C1z", "%2,%0"); |
| branch[inverted_p] = LARCH_BRANCH ("b%N1z", "%2,%0"); |
| } |
| else |
| { |
| branch[!inverted_p] = LARCH_BRANCH ("b%C1", "%2,%z3,%0"); |
| branch[inverted_p] = LARCH_BRANCH ("b%N1", "%2,%z3,%0"); |
| } |
| |
| return loongarch_output_conditional_branch (insn, operands, branch[1], |
| branch[0]); |
| } |
| |
| /* Return the assembly code for INSN, which branches to OPERANDS[0] |
| if some ordering condition is true. The condition is given by |
| OPERANDS[1] if !INVERTED_P, otherwise it is the inverse of |
| OPERANDS[1]. OPERANDS[2] is the comparison's first operand; |
| OPERANDS[3] is the second operand and may be zero or a register. */ |
| |
| const char * |
| loongarch_output_order_conditional_branch (rtx_insn *insn, rtx *operands, |
| bool inverted_p) |
| { |
| const char *branch[2]; |
| |
| /* Make BRANCH[1] branch to OPERANDS[0] when the condition is true. |
| Make BRANCH[0] branch on the inverse condition. */ |
| if (operands[3] != const0_rtx) |
| { |
| /* Handle degenerate cases that should not, but do, occur. */ |
| if (REGNO (operands[2]) == REGNO (operands[3])) |
| { |
| switch (GET_CODE (operands[1])) |
| { |
| case LT: |
| case LTU: |
| case GT: |
| case GTU: |
| inverted_p = !inverted_p; |
| /* Fall through. */ |
| case LE: |
| case LEU: |
| case GE: |
| case GEU: |
| branch[!inverted_p] = LARCH_BRANCH ("b", "%0"); |
| branch[inverted_p] = "\t# branch never"; |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| else |
| { |
| switch (GET_CODE (operands[1])) |
| { |
| case LE: |
| case LEU: |
| case GT: |
| case GTU: |
| case LT: |
| case LTU: |
| case GE: |
| case GEU: |
| branch[!inverted_p] = LARCH_BRANCH ("b%C1", "%2,%3,%0"); |
| branch[inverted_p] = LARCH_BRANCH ("b%N1", "%2,%3,%0"); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| } |
| else |
| { |
| switch (GET_CODE (operands[1])) |
| { |
| /* These cases are equivalent to comparisons against zero. */ |
| case LEU: |
| case GTU: |
| case LTU: |
| case GEU: |
| case LE: |
| case GT: |
| case LT: |
| case GE: |
| branch[!inverted_p] = LARCH_BRANCH ("b%C1", "%2,$r0,%0"); |
| branch[inverted_p] = LARCH_BRANCH ("b%N1", "%2,$r0,%0"); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| return loongarch_output_conditional_branch (insn, operands, branch[1], |
| branch[0]); |
| } |
| |
| /* Return the assembly code for DIV.{W/D} instruction DIVISION, which has |
| the operands given by OPERANDS. Add in a divide-by-zero check if needed. |
| */ |
| |
| const char * |
| loongarch_output_division (const char *division, rtx *operands) |
| { |
| const char *s; |
| |
| s = division; |
| if (loongarch_check_zero_div_p ()) |
| { |
| output_asm_insn (s, operands); |
| s = "bne\t%2,%.,1f\n\tbreak\t7\n1:"; |
| } |
| return s; |
| } |
| |
| /* Implement TARGET_SCHED_ADJUST_COST. We assume that anti and output |
| dependencies have no cost. */ |
| |
| static int |
| loongarch_adjust_cost (rtx_insn *, int dep_type, rtx_insn *, int cost, |
| unsigned int) |
| { |
| if (dep_type != 0 && (dep_type != REG_DEP_OUTPUT)) |
| return 0; |
| return cost; |
| } |
| |
| /* Return the number of instructions that can be issued per cycle. */ |
| |
| static int |
| loongarch_issue_rate (void) |
| { |
| if ((unsigned long) LARCH_ACTUAL_TUNE < N_TUNE_TYPES) |
| return loongarch_cpu_issue_rate[LARCH_ACTUAL_TUNE]; |
| else |
| return 1; |
| } |
| |
| /* Implement TARGET_SCHED_FIRST_CYCLE_MULTIPASS_DFA_LOOKAHEAD. This should |
| be as wide as the scheduling freedom in the DFA. */ |
| |
| static int |
| loongarch_multipass_dfa_lookahead (void) |
| { |
| if ((unsigned long) LARCH_ACTUAL_TUNE < N_ARCH_TYPES) |
| return loongarch_cpu_multipass_dfa_lookahead[LARCH_ACTUAL_TUNE]; |
| else |
| return 0; |
| } |
| |
| /* Implement TARGET_SCHED_REORDER. */ |
| |
| static int |
| loongarch_sched_reorder (FILE *file ATTRIBUTE_UNUSED, |
| int verbose ATTRIBUTE_UNUSED, |
| rtx_insn **ready ATTRIBUTE_UNUSED, |
| int *nreadyp ATTRIBUTE_UNUSED, |
| int cycle ATTRIBUTE_UNUSED) |
| { |
| return loongarch_issue_rate (); |
| } |
| |
| /* Implement TARGET_SCHED_REORDER2. */ |
| |
| static int |
| loongarch_sched_reorder2 (FILE *file ATTRIBUTE_UNUSED, |
| int verbose ATTRIBUTE_UNUSED, |
| rtx_insn **ready ATTRIBUTE_UNUSED, |
| int *nreadyp ATTRIBUTE_UNUSED, |
| int cycle ATTRIBUTE_UNUSED) |
| { |
| return cached_can_issue_more; |
| } |
| |
| /* Implement TARGET_SCHED_INIT. */ |
| |
| static void |
| loongarch_sched_init (FILE *file ATTRIBUTE_UNUSED, |
| int verbose ATTRIBUTE_UNUSED, |
| int max_ready ATTRIBUTE_UNUSED) |
| {} |
| |
| /* Implement TARGET_SCHED_VARIABLE_ISSUE. */ |
| |
| static int |
| loongarch_variable_issue (FILE *file ATTRIBUTE_UNUSED, |
| int verbose ATTRIBUTE_UNUSED, rtx_insn *insn, |
| int more) |
| { |
| /* Ignore USEs and CLOBBERs; don't count them against the issue rate. */ |
| if (USEFUL_INSN_P (insn)) |
| { |
| if (get_attr_type (insn) != TYPE_GHOST) |
| more--; |
| } |
| |
| /* Instructions of type 'multi' should all be split before |
| the second scheduling pass. */ |
| gcc_assert (!reload_completed |
| || recog_memoized (insn) < 0 |
| || get_attr_type (insn) != TYPE_MULTI); |
| |
| cached_can_issue_more = more; |
| return more; |
| } |
| |
| /* Given that we have an rtx of the form (prefetch ... WRITE LOCALITY), |
| return the first operand of the associated PREF or PREFX insn. */ |
| |
| rtx |
| loongarch_prefetch_cookie (rtx write, rtx locality) |
| { |
| /* store_streamed / load_streamed. */ |
| if (INTVAL (locality) <= 0) |
| return GEN_INT (INTVAL (write) + 4); |
| |
| /* store / load. */ |
| if (INTVAL (locality) <= 2) |
| return write; |
| |
| /* store_retained / load_retained. */ |
| return GEN_INT (INTVAL (write) + 6); |
| } |
| |
| /* Implement TARGET_ASM_OUTPUT_MI_THUNK. Generate rtl rather than asm text |
| in order to avoid duplicating too much logic from elsewhere. */ |
| |
| static void |
| loongarch_output_mi_thunk (FILE *file, tree thunk_fndecl ATTRIBUTE_UNUSED, |
| HOST_WIDE_INT delta, HOST_WIDE_INT vcall_offset, |
| tree function) |
| { |
| const char *fnname = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (thunk_fndecl)); |
| rtx this_rtx, temp1, temp2, fnaddr; |
| rtx_insn *insn; |
| bool use_sibcall_p; |
| |
| /* Pretend to be a post-reload pass while generating rtl. */ |
| reload_completed = 1; |
| |
| /* Mark the end of the (empty) prologue. */ |
| emit_note (NOTE_INSN_PROLOGUE_END); |
| |
| /* Determine if we can use a sibcall to call FUNCTION directly. */ |
| fnaddr = XEXP (DECL_RTL (function), 0); |
| use_sibcall_p = const_call_insn_operand (fnaddr, Pmode); |
| |
| /* We need two temporary registers in some cases. */ |
| temp1 = gen_rtx_REG (Pmode, 12); |
| temp2 = gen_rtx_REG (Pmode, 13); |
| |
| /* Find out which register contains the "this" pointer. */ |
| if (aggregate_value_p (TREE_TYPE (TREE_TYPE (function)), function)) |
| this_rtx = gen_rtx_REG (Pmode, GP_ARG_FIRST + 1); |
| else |
| this_rtx = gen_rtx_REG (Pmode, GP_ARG_FIRST); |
| |
| /* Add DELTA to THIS_RTX. */ |
| if (delta != 0) |
| { |
| rtx offset = GEN_INT (delta); |
| if (!IMM12_OPERAND (delta)) |
| { |
| loongarch_emit_move (temp1, offset); |
| offset = temp1; |
| } |
| emit_insn (gen_add3_insn (this_rtx, this_rtx, offset)); |
| } |
| |
| /* If needed, add *(*THIS_RTX + VCALL_OFFSET) to THIS_RTX. */ |
| if (vcall_offset != 0) |
| { |
| rtx addr; |
| |
| /* Set TEMP1 to *THIS_RTX. */ |
| loongarch_emit_move (temp1, gen_rtx_MEM (Pmode, this_rtx)); |
| |
| /* Set ADDR to a legitimate address for *THIS_RTX + VCALL_OFFSET. */ |
| addr = loongarch_add_offset (temp2, temp1, vcall_offset); |
| |
| /* Load the offset and add it to THIS_RTX. */ |
| loongarch_emit_move (temp1, gen_rtx_MEM (Pmode, addr)); |
| emit_insn (gen_add3_insn (this_rtx, this_rtx, temp1)); |
| } |
| |
| /* Jump to the target function. Use a sibcall if direct jumps are |
| allowed, otherwise load the address into a register first. */ |
| if (use_sibcall_p) |
| { |
| insn = emit_call_insn (gen_sibcall_internal (fnaddr, const0_rtx)); |
| SIBLING_CALL_P (insn) = 1; |
| } |
| else |
| { |
| loongarch_emit_move (temp1, fnaddr); |
| emit_jump_insn (gen_indirect_jump (temp1)); |
| } |
| |
| /* Run just enough of rest_of_compilation. This sequence was |
| "borrowed" from alpha.c. */ |
| insn = get_insns (); |
| split_all_insns_noflow (); |
| shorten_branches (insn); |
| assemble_start_function (thunk_fndecl, fnname); |
| final_start_function (insn, file, 1); |
| final (insn, file, 1); |
| final_end_function (); |
| assemble_end_function (thunk_fndecl, fnname); |
| |
| /* Stop pretending to be a post-reload pass. */ |
| reload_completed = 0; |
| } |
| |
| /* Allocate a chunk of memory for per-function machine-dependent data. */ |
| |
| static struct machine_function * |
| loongarch_init_machine_status (void) |
| { |
| return ggc_cleared_alloc<machine_function> (); |
| } |
| |
| static void |
| loongarch_option_override_internal (struct gcc_options *opts) |
| { |
| int i, regno, mode; |
| |
| if (flag_pic) |
| g_switch_value = 0; |
| |
| /* Handle target-specific options: compute defaults/conflicts etc. */ |
| loongarch_config_target (&la_target, la_opt_switches, |
| la_opt_cpu_arch, la_opt_cpu_tune, la_opt_fpu, |
| la_opt_abi_base, la_opt_abi_ext, la_opt_cmodel, 0); |
| |
| if (TARGET_ABI_LP64) |
| flag_pcc_struct_return = 0; |
| |
| /* Decide which rtx_costs structure to use. */ |
| if (optimize_size) |
| loongarch_cost = &loongarch_rtx_cost_optimize_size; |
| else |
| loongarch_cost = &loongarch_cpu_rtx_cost_data[LARCH_ACTUAL_TUNE]; |
| |
| /* If the user hasn't specified a branch cost, use the processor's |
| default. */ |
| if (loongarch_branch_cost == 0) |
| loongarch_branch_cost = loongarch_cost->branch_cost; |
| |
| /* Set up parameters to be used in prefetching algorithm. */ |
| int simultaneous_prefetches |
| = loongarch_cpu_cache[LARCH_ACTUAL_TUNE].simultaneous_prefetches; |
| |
| SET_OPTION_IF_UNSET (opts, &global_options_set, |
| param_simultaneous_prefetches, |
| simultaneous_prefetches); |
| |
| SET_OPTION_IF_UNSET (opts, &global_options_set, |
| param_l1_cache_line_size, |
| loongarch_cpu_cache[LARCH_ACTUAL_TUNE].l1d_line_size); |
| |
| SET_OPTION_IF_UNSET (opts, &global_options_set, |
| param_l1_cache_size, |
| loongarch_cpu_cache[LARCH_ACTUAL_TUNE].l1d_size); |
| |
| SET_OPTION_IF_UNSET (opts, &global_options_set, |
| param_l2_cache_size, |
| loongarch_cpu_cache[LARCH_ACTUAL_TUNE].l2d_size); |
| |
| |
| /* Enable sw prefetching at -O3 and higher. */ |
| if (opts->x_flag_prefetch_loop_arrays < 0 |
| && (opts->x_optimize >= 3 || opts->x_flag_profile_use) |
| && !opts->x_optimize_size) |
| opts->x_flag_prefetch_loop_arrays = 1; |
| |
| if (TARGET_DIRECT_EXTERN_ACCESS && flag_shlib) |
| error ("%qs cannot be used for compiling a shared library", |
| "-mdirect-extern-access"); |
| |
| switch (la_target.cmodel) |
| { |
| case CMODEL_EXTREME: |
| if (!TARGET_EXPLICIT_RELOCS) |
| error ("code model %qs needs %s", |
| "extreme", "-mexplicit-relocs"); |
| |
| if (opts->x_flag_plt) |
| { |
| if (global_options_set.x_flag_plt) |
| error ("code model %qs is not compatible with %s", |
| "extreme", "-fplt"); |
| opts->x_flag_plt = 0; |
| } |
| break; |
| |
| case CMODEL_TINY_STATIC: |
| case CMODEL_MEDIUM: |
| case CMODEL_NORMAL: |
| case CMODEL_TINY: |
| case CMODEL_LARGE: |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| loongarch_init_print_operand_punct (); |
| |
| /* Set up array to map GCC register number to debug register number. |
| Ignore the special purpose register numbers. */ |
| |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| { |
| if (GP_REG_P (i) || FP_REG_P (i)) |
| loongarch_dwarf_regno[i] = i; |
| else |
| loongarch_dwarf_regno[i] = INVALID_REGNUM; |
| } |
| |
| /* Set up loongarch_hard_regno_mode_ok. */ |
| for (mode = 0; mode < MAX_MACHINE_MODE; mode++) |
| for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++) |
| loongarch_hard_regno_mode_ok_p[mode][regno] |
| = loongarch_hard_regno_mode_ok_uncached (regno, (machine_mode) mode); |
| |
| /* Function to allocate machine-dependent function status. */ |
| init_machine_status = &loongarch_init_machine_status; |
| } |
| |
| |
| /* Implement TARGET_OPTION_OVERRIDE. */ |
| |
| static void |
| loongarch_option_override (void) |
| { |
| loongarch_option_override_internal (&global_options); |
| } |
| |
| /* Implement TARGET_CONDITIONAL_REGISTER_USAGE. */ |
| |
| static void |
| loongarch_conditional_register_usage (void) |
| { |
| if (!TARGET_HARD_FLOAT) |
| accessible_reg_set &= ~(reg_class_contents[FP_REGS] |
| | reg_class_contents[FCC_REGS]); |
| } |
| |
| /* Implement EH_USES. */ |
| |
| bool |
| loongarch_eh_uses (unsigned int regno ATTRIBUTE_UNUSED) |
| { |
| return false; |
| } |
| |
| /* Implement EPILOGUE_USES. */ |
| |
| bool |
| loongarch_epilogue_uses (unsigned int regno) |
| { |
| /* Say that the epilogue uses the return address register. Note that |
| in the case of sibcalls, the values "used by the epilogue" are |
| considered live at the start of the called function. */ |
| if (regno == RETURN_ADDR_REGNUM) |
| return true; |
| |
| return false; |
| } |
| |
| bool |
| loongarch_load_store_bonding_p (rtx *operands, machine_mode mode, bool load_p) |
| { |
| rtx reg1, reg2, mem1, mem2, base1, base2; |
| enum reg_class rc1, rc2; |
| HOST_WIDE_INT offset1, offset2; |
| |
| if (load_p) |
| { |
| reg1 = operands[0]; |
| reg2 = operands[2]; |
| mem1 = operands[1]; |
| mem2 = operands[3]; |
| } |
| else |
| { |
| reg1 = operands[1]; |
| reg2 = operands[3]; |
| mem1 = operands[0]; |
| mem2 = operands[2]; |
| } |
| |
| if (loongarch_address_insns (XEXP (mem1, 0), mode, false) == 0 |
| || loongarch_address_insns (XEXP (mem2, 0), mode, false) == 0) |
| return false; |
| |
| loongarch_split_plus (XEXP (mem1, 0), &base1, &offset1); |
| loongarch_split_plus (XEXP (mem2, 0), &base2, &offset2); |
| |
| /* Base regs do not match. */ |
| if (!REG_P (base1) || !rtx_equal_p (base1, base2)) |
| return false; |
| |
| /* Either of the loads is clobbering base register. It is legitimate to bond |
| loads if second load clobbers base register. However, hardware does not |
| support such bonding. */ |
| if (load_p |
| && (REGNO (reg1) == REGNO (base1) || (REGNO (reg2) == REGNO (base1)))) |
| return false; |
| |
| /* Loading in same registers. */ |
| if (load_p && REGNO (reg1) == REGNO (reg2)) |
| return false; |
| |
| /* The loads/stores are not of same type. */ |
| rc1 = REGNO_REG_CLASS (REGNO (reg1)); |
| rc2 = REGNO_REG_CLASS (REGNO (reg2)); |
| if (rc1 != rc2 && !reg_class_subset_p (rc1, rc2) |
| && !reg_class_subset_p (rc2, rc1)) |
| return false; |
| |
| if (abs (offset1 - offset2) != GET_MODE_SIZE (mode)) |
| return false; |
| |
| return true; |
| } |
| |
| /* Implement TARGET_TRAMPOLINE_INIT. */ |
| |
| static void |
| loongarch_trampoline_init (rtx m_tramp, tree fndecl, rtx chain_value) |
| { |
| rtx addr, end_addr, mem; |
| rtx trampoline[8]; |
| unsigned int i, j; |
| HOST_WIDE_INT end_addr_offset, static_chain_offset, target_function_offset; |
| |
| /* Work out the offsets of the pointers from the start of the |
| trampoline code. */ |
| end_addr_offset = TRAMPOLINE_CODE_SIZE; |
| static_chain_offset = end_addr_offset; |
| target_function_offset = static_chain_offset + GET_MODE_SIZE (ptr_mode); |
| |
| /* Get pointers to the beginning and end of the code block. */ |
| addr = force_reg (Pmode, XEXP (m_tramp, 0)); |
| end_addr |
| = loongarch_force_binary (Pmode, PLUS, addr, GEN_INT (end_addr_offset)); |
| |
| #define OP(X) gen_int_mode (X, SImode) |
| |
| /* Build up the code in TRAMPOLINE. */ |
| i = 0; |
| /*pcaddi $static_chain,0 |
| ld.[dw] $tmp,$static_chain,target_function_offset |
| ld.[dw] $static_chain,$static_chain,static_chain_offset |
| jirl $r0,$tmp,0 */ |
| trampoline[i++] = OP (0x18000000 | (STATIC_CHAIN_REGNUM - GP_REG_FIRST)); |
| trampoline[i++] = OP ((ptr_mode == DImode ? 0x28c00000 : 0x28800000) |
| | 19 /* $t7 */ |
| | ((STATIC_CHAIN_REGNUM - GP_REG_FIRST) << 5) |
| | ((target_function_offset & 0xfff) << 10)); |
| trampoline[i++] = OP ((ptr_mode == DImode ? 0x28c00000 : 0x28800000) |
| | (STATIC_CHAIN_REGNUM - GP_REG_FIRST) |
| | ((STATIC_CHAIN_REGNUM - GP_REG_FIRST) << 5) |
| | ((static_chain_offset & 0xfff) << 10)); |
| trampoline[i++] = OP (0x4c000000 | (19 << 5)); |
| #undef OP |
| |
| for (j = 0; j < i; j++) |
| { |
| mem = adjust_address (m_tramp, SImode, j * GET_MODE_SIZE (SImode)); |
| loongarch_emit_move (mem, trampoline[j]); |
| } |
| |
| /* Set up the static chain pointer field. */ |
| mem = adjust_address (m_tramp, ptr_mode, static_chain_offset); |
| loongarch_emit_move (mem, chain_value); |
| |
| /* Set up the target function field. */ |
| mem = adjust_address (m_tramp, ptr_mode, target_function_offset); |
| loongarch_emit_move (mem, XEXP (DECL_RTL (fndecl), 0)); |
| |
| /* Flush the code part of the trampoline. */ |
| emit_insn (gen_add3_insn (end_addr, addr, GEN_INT (TRAMPOLINE_SIZE))); |
| emit_insn (gen_clear_cache (addr, end_addr)); |
| } |
| |
| /* Implement HARD_REGNO_CALLER_SAVE_MODE. */ |
| |
| machine_mode |
| loongarch_hard_regno_caller_save_mode (unsigned int regno, unsigned int nregs, |
| machine_mode mode) |
| { |
| /* For performance, avoid saving/restoring upper parts of a register |
| by returning MODE as save mode when the mode is known. */ |
| if (mode == VOIDmode) |
| return choose_hard_reg_mode (regno, nregs, NULL); |
| else |
| return mode; |
| } |
| |
| /* Implement TARGET_SPILL_CLASS. */ |
| |
| static reg_class_t |
| loongarch_spill_class (reg_class_t rclass ATTRIBUTE_UNUSED, |
| machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return NO_REGS; |
| } |
| |
| /* Implement TARGET_PROMOTE_FUNCTION_MODE. */ |
| |
| /* This function is equivalent to default_promote_function_mode_always_promote |
| except that it returns a promoted mode even if type is NULL_TREE. This is |
| needed by libcalls which have no type (only a mode) such as fixed conversion |
| routines that take a signed or unsigned char/short argument and convert it |
| to a fixed type. */ |
| |
| static machine_mode |
| loongarch_promote_function_mode (const_tree type ATTRIBUTE_UNUSED, |
| machine_mode mode, |
| int *punsignedp ATTRIBUTE_UNUSED, |
| const_tree fntype ATTRIBUTE_UNUSED, |
| int for_return ATTRIBUTE_UNUSED) |
| { |
| int unsignedp; |
| |
| if (type != NULL_TREE) |
| return promote_mode (type, mode, punsignedp); |
| |
| unsignedp = *punsignedp; |
| PROMOTE_MODE (mode, unsignedp, type); |
| *punsignedp = unsignedp; |
| return mode; |
| } |
| |
| /* Implement TARGET_STARTING_FRAME_OFFSET. See loongarch_compute_frame_info |
| for details about the frame layout. */ |
| |
| static HOST_WIDE_INT |
| loongarch_starting_frame_offset (void) |
| { |
| if (FRAME_GROWS_DOWNWARD) |
| return 0; |
| return crtl->outgoing_args_size; |
| } |
| |
| static tree |
| loongarch_handle_model_attribute (tree *node, tree name, tree arg, int, |
| bool *no_add_attrs) |
| { |
| tree decl = *node; |
| if (TREE_CODE (decl) == VAR_DECL) |
| { |
| if (DECL_THREAD_LOCAL_P (decl)) |
| { |
| error_at (DECL_SOURCE_LOCATION (decl), |
| "%qE attribute cannot be specified for thread-local " |
| "variables", name); |
| *no_add_attrs = true; |
| return NULL_TREE; |
| } |
| if (DECL_CONTEXT (decl) |
| && TREE_CODE (DECL_CONTEXT (decl)) == FUNCTION_DECL |
| && !TREE_STATIC (decl)) |
| { |
| error_at (DECL_SOURCE_LOCATION (decl), |
| "%qE attribute cannot be specified for local " |
| "variables", name); |
| *no_add_attrs = true; |
| return NULL_TREE; |
| } |
| if (DECL_REGISTER (decl)) |
| { |
| error_at (DECL_SOURCE_LOCATION (decl), |
| "%qE attribute cannot be specified for register " |
| "variables", name); |
| *no_add_attrs = true; |
| return NULL_TREE; |
| } |
| if (!TARGET_EXPLICIT_RELOCS) |
| { |
| error_at (DECL_SOURCE_LOCATION (decl), |
| "%qE attribute requires %s", name, "-mexplicit-relocs"); |
| *no_add_attrs = true; |
| return NULL_TREE; |
| } |
| |
| arg = TREE_VALUE (arg); |
| if (TREE_CODE (arg) != STRING_CST) |
| { |
| error_at (DECL_SOURCE_LOCATION (decl), |
| "invalid argument of %qE attribute", name); |
| *no_add_attrs = true; |
| return NULL_TREE; |
| } |
| |
| const char *model = TREE_STRING_POINTER (arg); |
| if (strcmp (model, "normal") != 0 |
| && strcmp (model, "extreme") != 0) |
| { |
| error_at (DECL_SOURCE_LOCATION (decl), |
| "invalid argument of %qE attribute", name); |
| *no_add_attrs = true; |
| return NULL_TREE; |
| } |
| |
| if (lookup_attribute ("model", DECL_ATTRIBUTES (decl))) |
| { |
| error_at (DECL_SOURCE_LOCATION (decl), |
| "multiple %qE attribute", name); |
| *no_add_attrs = true; |
| return NULL_TREE; |
| } |
| } |
| else |
| { |
| warning (OPT_Wattributes, "%qE attribute ignored", name); |
| *no_add_attrs = true; |
| } |
| return NULL_TREE; |
| } |
| |
| static const struct attribute_spec loongarch_attribute_table[] = |
| { |
| /* { name, min_len, max_len, decl_req, type_req, fn_type_req, |
| affects_type_identity, handler, exclude } */ |
| { "model", 1, 1, true, false, false, false, |
| loongarch_handle_model_attribute, NULL }, |
| /* The last attribute spec is set to be NULL. */ |
| {} |
| }; |
| |
| bool |
| loongarch_use_anchors_for_symbol_p (const_rtx symbol) |
| { |
| tree decl = SYMBOL_REF_DECL (symbol); |
| |
| /* The section anchor optimization may break custom address model. */ |
| if (decl && lookup_attribute ("model", DECL_ATTRIBUTES (decl))) |
| return false; |
| |
| return default_use_anchors_for_symbol_p (symbol); |
| } |
| |
| /* Implement the TARGET_ASAN_SHADOW_OFFSET hook. */ |
| |
| static unsigned HOST_WIDE_INT |
| loongarch_asan_shadow_offset (void) |
| { |
| /* We only have libsanitizer support for LOONGARCH64 at present. |
| This value is taken from the file libsanitizer/asan/asan_mapping.h. */ |
| return TARGET_64BIT ? (HOST_WIDE_INT_1 << 46) : 0; |
| } |
| |
| /* Initialize the GCC target structure. */ |
| #undef TARGET_ASM_ALIGNED_HI_OP |
| #define TARGET_ASM_ALIGNED_HI_OP "\t.half\t" |
| #undef TARGET_ASM_ALIGNED_SI_OP |
| #define TARGET_ASM_ALIGNED_SI_OP "\t.word\t" |
| #undef TARGET_ASM_ALIGNED_DI_OP |
| #define TARGET_ASM_ALIGNED_DI_OP "\t.dword\t" |
| |
| #undef TARGET_OPTION_OVERRIDE |
| #define TARGET_OPTION_OVERRIDE loongarch_option_override |
| |
| #undef TARGET_LEGITIMIZE_ADDRESS |
| #define TARGET_LEGITIMIZE_ADDRESS loongarch_legitimize_address |
| |
| #undef TARGET_ASM_SELECT_RTX_SECTION |
| #define TARGET_ASM_SELECT_RTX_SECTION loongarch_select_rtx_section |
| #undef TARGET_ASM_FUNCTION_RODATA_SECTION |
| #define TARGET_ASM_FUNCTION_RODATA_SECTION loongarch_function_rodata_section |
| |
| #undef TARGET_SCHED_INIT |
| #define TARGET_SCHED_INIT loongarch_sched_init |
| #undef TARGET_SCHED_REORDER |
| #define TARGET_SCHED_REORDER loongarch_sched_reorder |
| #undef TARGET_SCHED_REORDER2 |
| #define TARGET_SCHED_REORDER2 loongarch_sched_reorder2 |
| #undef TARGET_SCHED_VARIABLE_ISSUE |
| #define TARGET_SCHED_VARIABLE_ISSUE loongarch_variable_issue |
| #undef TARGET_SCHED_ADJUST_COST |
| #define TARGET_SCHED_ADJUST_COST loongarch_adjust_cost |
| #undef TARGET_SCHED_ISSUE_RATE |
| #define TARGET_SCHED_ISSUE_RATE loongarch_issue_rate |
| #undef TARGET_SCHED_FIRST_CYCLE_MULTIPASS_DFA_LOOKAHEAD |
| #define TARGET_SCHED_FIRST_CYCLE_MULTIPASS_DFA_LOOKAHEAD \ |
| loongarch_multipass_dfa_lookahead |
| |
| #undef TARGET_FUNCTION_OK_FOR_SIBCALL |
| #define TARGET_FUNCTION_OK_FOR_SIBCALL loongarch_function_ok_for_sibcall |
| |
| #undef TARGET_VALID_POINTER_MODE |
| #define TARGET_VALID_POINTER_MODE loongarch_valid_pointer_mode |
| #undef TARGET_REGISTER_MOVE_COST |
| #define TARGET_REGISTER_MOVE_COST loongarch_register_move_cost |
| #undef TARGET_MEMORY_MOVE_COST |
| #define TARGET_MEMORY_MOVE_COST loongarch_memory_move_cost |
| #undef TARGET_RTX_COSTS |
| #define TARGET_RTX_COSTS loongarch_rtx_costs |
| #undef TARGET_ADDRESS_COST |
| #define TARGET_ADDRESS_COST loongarch_address_cost |
| |
| #undef TARGET_IN_SMALL_DATA_P |
| #define TARGET_IN_SMALL_DATA_P loongarch_in_small_data_p |
| |
| #undef TARGET_PREFERRED_RELOAD_CLASS |
| #define TARGET_PREFERRED_RELOAD_CLASS loongarch_preferred_reload_class |
| |
| #undef TARGET_ASM_FILE_START_FILE_DIRECTIVE |
| #define TARGET_ASM_FILE_START_FILE_DIRECTIVE true |
| |
| #undef TARGET_EXPAND_BUILTIN_VA_START |
| #define TARGET_EXPAND_BUILTIN_VA_START loongarch_va_start |
| |
| #undef TARGET_PROMOTE_FUNCTION_MODE |
| #define TARGET_PROMOTE_FUNCTION_MODE loongarch_promote_function_mode |
| #undef TARGET_RETURN_IN_MEMORY |
| #define TARGET_RETURN_IN_MEMORY loongarch_return_in_memory |
| |
| #undef TARGET_FUNCTION_VALUE |
| #define TARGET_FUNCTION_VALUE loongarch_function_value |
| #undef TARGET_LIBCALL_VALUE |
| #define TARGET_LIBCALL_VALUE loongarch_libcall_value |
| |
| #undef TARGET_ASM_OUTPUT_MI_THUNK |
| #define TARGET_ASM_OUTPUT_MI_THUNK loongarch_output_mi_thunk |
| #undef TARGET_ASM_CAN_OUTPUT_MI_THUNK |
| #define TARGET_ASM_CAN_OUTPUT_MI_THUNK \ |
| hook_bool_const_tree_hwi_hwi_const_tree_true |
| |
| #undef TARGET_PRINT_OPERAND |
| #define TARGET_PRINT_OPERAND loongarch_print_operand |
| #undef TARGET_PRINT_OPERAND_ADDRESS |
| #define TARGET_PRINT_OPERAND_ADDRESS loongarch_print_operand_address |
| #undef TARGET_PRINT_OPERAND_PUNCT_VALID_P |
| #define TARGET_PRINT_OPERAND_PUNCT_VALID_P \ |
| loongarch_print_operand_punct_valid_p |
| |
| #undef TARGET_SETUP_INCOMING_VARARGS |
| #define TARGET_SETUP_INCOMING_VARARGS loongarch_setup_incoming_varargs |
| #undef TARGET_STRICT_ARGUMENT_NAMING |
| #define TARGET_STRICT_ARGUMENT_NAMING hook_bool_CUMULATIVE_ARGS_true |
| #undef TARGET_MUST_PASS_IN_STACK |
| #define TARGET_MUST_PASS_IN_STACK must_pass_in_stack_var_size |
| #undef TARGET_PASS_BY_REFERENCE |
| #define TARGET_PASS_BY_REFERENCE loongarch_pass_by_reference |
| #undef TARGET_ARG_PARTIAL_BYTES |
| #define TARGET_ARG_PARTIAL_BYTES loongarch_arg_partial_bytes |
| #undef TARGET_FUNCTION_ARG |
| #define TARGET_FUNCTION_ARG loongarch_function_arg |
| #undef TARGET_FUNCTION_ARG_ADVANCE |
| #define TARGET_FUNCTION_ARG_ADVANCE loongarch_function_arg_advance |
| #undef TARGET_FUNCTION_ARG_BOUNDARY |
| #define TARGET_FUNCTION_ARG_BOUNDARY loongarch_function_arg_boundary |
| |
| #undef TARGET_SCALAR_MODE_SUPPORTED_P |
| #define TARGET_SCALAR_MODE_SUPPORTED_P loongarch_scalar_mode_supported_p |
| |
| #undef TARGET_INIT_BUILTINS |
| #define TARGET_INIT_BUILTINS loongarch_init_builtins |
| #undef TARGET_BUILTIN_DECL |
| #define TARGET_BUILTIN_DECL loongarch_builtin_decl |
| #undef TARGET_EXPAND_BUILTIN |
| #define TARGET_EXPAND_BUILTIN loongarch_expand_builtin |
| |
| /* The generic ELF target does not always have TLS support. */ |
| #ifdef HAVE_AS_TLS |
| #undef TARGET_HAVE_TLS |
| #define TARGET_HAVE_TLS HAVE_AS_TLS |
| #endif |
| |
| #undef TARGET_CANNOT_FORCE_CONST_MEM |
| #define TARGET_CANNOT_FORCE_CONST_MEM loongarch_cannot_force_const_mem |
| |
| #undef TARGET_LEGITIMATE_CONSTANT_P |
| #define TARGET_LEGITIMATE_CONSTANT_P loongarch_legitimate_constant_p |
| |
| #undef TARGET_USE_BLOCKS_FOR_CONSTANT_P |
| #define TARGET_USE_BLOCKS_FOR_CONSTANT_P hook_bool_mode_const_rtx_true |
| |
| #ifdef HAVE_AS_DTPRELWORD |
| #undef TARGET_ASM_OUTPUT_DWARF_DTPREL |
| #define TARGET_ASM_OUTPUT_DWARF_DTPREL loongarch_output_dwarf_dtprel |
| #endif |
| |
| #undef TARGET_LEGITIMATE_ADDRESS_P |
| #define TARGET_LEGITIMATE_ADDRESS_P loongarch_legitimate_address_p |
| |
| #undef TARGET_FRAME_POINTER_REQUIRED |
| #define TARGET_FRAME_POINTER_REQUIRED loongarch_frame_pointer_required |
| |
| #undef TARGET_CAN_ELIMINATE |
| #define TARGET_CAN_ELIMINATE loongarch_can_eliminate |
| |
| #undef TARGET_CONDITIONAL_REGISTER_USAGE |
| #define TARGET_CONDITIONAL_REGISTER_USAGE loongarch_conditional_register_usage |
| |
| #undef TARGET_TRAMPOLINE_INIT |
| #define TARGET_TRAMPOLINE_INIT loongarch_trampoline_init |
| |
| #undef TARGET_MIN_ANCHOR_OFFSET |
| #define TARGET_MIN_ANCHOR_OFFSET (-IMM_REACH/2) |
| |
| #undef TARGET_MAX_ANCHOR_OFFSET |
| #define TARGET_MAX_ANCHOR_OFFSET (IMM_REACH/2-1) |
| |
| #undef TARGET_ATOMIC_ASSIGN_EXPAND_FENV |
| #define TARGET_ATOMIC_ASSIGN_EXPAND_FENV loongarch_atomic_assign_expand_fenv |
| |
| #undef TARGET_CALL_FUSAGE_CONTAINS_NON_CALLEE_CLOBBERS |
| #define TARGET_CALL_FUSAGE_CONTAINS_NON_CALLEE_CLOBBERS true |
| |
| #undef TARGET_SPILL_CLASS |
| #define TARGET_SPILL_CLASS loongarch_spill_class |
| |
| #undef TARGET_HARD_REGNO_NREGS |
| #define TARGET_HARD_REGNO_NREGS loongarch_hard_regno_nregs |
| #undef TARGET_HARD_REGNO_MODE_OK |
| #define TARGET_HARD_REGNO_MODE_OK loongarch_hard_regno_mode_ok |
| |
| #undef TARGET_MODES_TIEABLE_P |
| #define TARGET_MODES_TIEABLE_P loongarch_modes_tieable_p |
| |
| #undef TARGET_CUSTOM_FUNCTION_DESCRIPTORS |
| #define TARGET_CUSTOM_FUNCTION_DESCRIPTORS 2 |
| |
| #undef TARGET_CAN_CHANGE_MODE_CLASS |
| #define TARGET_CAN_CHANGE_MODE_CLASS loongarch_can_change_mode_class |
| |
| #undef TARGET_CONSTANT_ALIGNMENT |
| #define TARGET_CONSTANT_ALIGNMENT loongarch_constant_alignment |
| |
| #undef TARGET_STARTING_FRAME_OFFSET |
| #define TARGET_STARTING_FRAME_OFFSET loongarch_starting_frame_offset |
| |
| #undef TARGET_SECONDARY_RELOAD |
| #define TARGET_SECONDARY_RELOAD loongarch_secondary_reload |
| |
| #undef TARGET_HAVE_SPECULATION_SAFE_VALUE |
| #define TARGET_HAVE_SPECULATION_SAFE_VALUE speculation_safe_value_not_needed |
| |
| #undef TARGET_ATTRIBUTE_TABLE |
| #define TARGET_ATTRIBUTE_TABLE loongarch_attribute_table |
| |
| #undef TARGET_USE_ANCHORS_FOR_SYMBOL_P |
| #define TARGET_USE_ANCHORS_FOR_SYMBOL_P loongarch_use_anchors_for_symbol_p |
| |
| #undef TARGET_ASAN_SHADOW_OFFSET |
| #define TARGET_ASAN_SHADOW_OFFSET loongarch_asan_shadow_offset |
| |
| struct gcc_target targetm = TARGET_INITIALIZER; |
| |
| #include "gt-loongarch.h" |