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