#define INTERNAL_SYMBOL_P(SYM) \
(XSTR (SYM, 0)[0] == '*' && XSTR (SYM, 0)[1] == LOCAL_LABEL_PREFIX[0])
+/* 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 mips_symbol_type) (XINT (X, 1) - UNSPEC_ADDRESS_FIRST))
+
+/* True if X is (const $gp). This is used to initialize the mips16
+ gp pseudo register. */
+#define CONST_GP_P(X) \
+ (GET_CODE (X) == CONST && XEXP (X, 0) == pic_offset_table_rtx)
+
/* The maximum distance between the top of the stack frame and the
value $sp has when we save & restore registers.
multi-instruction addu sequence. Use 0x7fe0 to work around this. */
#define MIPS_MAX_FIRST_STACK_STEP (TARGET_MIPS16 ? 0x100 : 0x7fe0)
-/* Classifies a non-literal integer constant.
-
- CONSTANT_NONE
- Not one of the constants below.
-
- CONSTANT_GP
- The global pointer, treated as a constant when TARGET_MIPS16.
- The rtx has the form:
-
- (const (reg $gp)).
-
- CONSTANT_RELOC
- A signed 16-bit relocation against either a symbol
- or a symbol plus an offset. The relocation has the form:
-
- (unspec [(SYMBOL) ...] RELOC)
-
- Any offset is added outside the unspec, such as:
-
- (plus (unspec [(SYMBOL) ...] RELOC) (const_int OFFSET))
-
- In either case, the whole expression is wrapped in a (const ...).
-
- CONSTANT_SYMBOLIC
- A reference to a symbol, possibly with an offset. */
-enum mips_constant_type {
- CONSTANT_NONE,
- CONSTANT_GP,
- CONSTANT_RELOC,
- CONSTANT_SYMBOLIC
-};
-
-
-/* Classifies a SYMBOL_REF or LABEL_REF.
+/* Classifies a SYMBOL_REF, LABEL_REF or UNSPEC address.
SYMBOL_GENERAL
Used when none of the below apply.
the global offset table.
SYMBOL_GOT_GLOBAL
- Likewise non-local data. */
+ Likewise non-local data.
+
+ SYMBOL_GOTOFF_PAGE
+ An UNSPEC wrapper around a SYMBOL_GOT_LOCAL. It represents the
+ offset from _gp of a GOT page entry.
+
+ SYMBOL_GOTOFF_GLOBAL
+ An UNSPEC wrapper around a SYMBOL_GOT_GLOBAL. It represents the
+ the offset from _gp of the symbol's GOT entry.
+
+ SYMBOL_GOTOFF_CALL
+ Like SYMBOL_GOTOFF_GLOBAL, but used when calling a global function.
+ The GOT entry is allowed to point to a stub rather than to the
+ function itself.
+
+ SYMBOL_GOTOFF_LOADGP
+ An UNSPEC wrapper around a function's address. It represents the
+ offset of _gp from the start of the function. */
enum mips_symbol_type {
SYMBOL_GENERAL,
SYMBOL_SMALL_DATA,
SYMBOL_CONSTANT_POOL,
SYMBOL_GOT_LOCAL,
- SYMBOL_GOT_GLOBAL
+ SYMBOL_GOT_GLOBAL,
+ SYMBOL_GOTOFF_PAGE,
+ SYMBOL_GOTOFF_GLOBAL,
+ SYMBOL_GOTOFF_CALL,
+ SYMBOL_GOTOFF_LOADGP
};
+#define NUM_SYMBOL_TYPES (SYMBOL_GOTOFF_LOADGP + 1)
/* Classifies an address.
- ADDRESS_INVALID
- The address should be rejected as invalid.
-
ADDRESS_REG
A natural register + offset address. The register satisfies
mips_valid_base_register_p and the offset is a const_arith_operand.
ADDRESS_SYMBOLIC:
A constant symbolic address (equivalent to CONSTANT_SYMBOLIC). */
enum mips_address_type {
- ADDRESS_INVALID,
ADDRESS_REG,
ADDRESS_LO_SUM,
ADDRESS_CONST_INT,
struct constant;
struct mips_arg_info;
-struct mips_constant_info;
struct mips_address_info;
struct mips_integer_op;
-static bool mips_reloc_offset_ok_p (int, HOST_WIDE_INT);
-static enum mips_constant_type
- mips_classify_constant (struct mips_constant_info *, rtx);
static enum mips_symbol_type mips_classify_symbol (rtx);
+static void mips_split_const (rtx, rtx *, HOST_WIDE_INT *);
+static bool mips_symbolic_constant_p (rtx, enum mips_symbol_type *);
static bool mips_valid_base_register_p (rtx, enum machine_mode, int);
-static bool mips_symbolic_address_p (rtx, HOST_WIDE_INT,
- enum machine_mode, int);
-static enum mips_address_type
- mips_classify_address (struct mips_address_info *, rtx,
- enum machine_mode, int, int);
-static bool mips_splittable_symbol_p (enum mips_symbol_type, HOST_WIDE_INT);
+static bool mips_symbolic_address_p (enum mips_symbol_type, enum machine_mode);
+static bool mips_classify_address (struct mips_address_info *, rtx,
+ enum machine_mode, int);
static int mips_symbol_insns (enum mips_symbol_type);
static bool mips16_unextended_reference_p (enum machine_mode mode, rtx, rtx);
-static rtx mips_reloc (rtx, int);
-static rtx mips_lui_reloc (rtx, int);
static rtx mips_force_temporary (rtx, rtx);
+static rtx mips_split_symbol (rtx, rtx);
+static rtx mips_unspec_address (rtx, enum mips_symbol_type);
+static rtx mips_unspec_offset_high (rtx, rtx, enum mips_symbol_type);
+static rtx mips_load_got (rtx, rtx, enum mips_symbol_type);
static rtx mips_add_offset (rtx, HOST_WIDE_INT);
-static rtx mips_load_got (rtx, rtx, int);
-static rtx mips_load_got16 (rtx, int);
-static rtx mips_load_got32 (rtx, rtx, int, int);
-static rtx mips_emit_high (rtx, rtx);
-static bool mips_legitimize_symbol (rtx, rtx *, int);
static unsigned int mips_build_shift (struct mips_integer_op *, HOST_WIDE_INT);
static unsigned int mips_build_lower (struct mips_integer_op *,
unsigned HOST_WIDE_INT);
static void mips_set_architecture (const struct mips_cpu_info *);
static void mips_set_tune (const struct mips_cpu_info *);
static struct machine_function *mips_init_machine_status (void);
-static const char *mips_reloc_string (int);
+static void print_operand_reloc (FILE *, rtx, const char **);
static bool mips_assemble_integer (rtx, unsigned int, int);
static void mips_file_start (void);
static void mips_file_end (void);
+static bool mips_rewrite_small_data_p (rtx);
+static int small_data_pattern_1 (rtx *, void *);
+static int mips_rewrite_small_data_1 (rtx *, void *);
+static bool mips_function_has_gp_insn (void);
static unsigned int mips_global_pointer (void);
static bool mips_save_reg_p (unsigned int);
static void mips_save_restore_reg (enum machine_mode, int, HOST_WIDE_INT,
static void mips_set_frame_expr (rtx);
static rtx mips_frame_set (rtx, rtx);
static void mips_save_reg (rtx, rtx);
-static void mips_gp_insn (rtx, rtx);
static void mips_output_function_epilogue (FILE *, HOST_WIDE_INT);
static void mips_restore_reg (rtx, rtx);
static int symbolic_expression_p (rtx);
ATTRIBUTE_UNUSED;
static bool mips_in_small_data_p (tree);
static void mips_encode_section_info (tree, rtx, int);
-static rtx mips_sdata_pointer (void);
+static rtx mips16_gp_pseudo_reg (void);
static void mips16_fp_args (FILE *, int, int);
static void build_mips16_function_stub (FILE *);
static void mips16_optimize_gp (void);
/* True if the whole function is suitable for .set noreorder and
.set nomacro. */
bool all_noreorder_p;
+
+ /* True if the function is known to have an instruction that needs $gp. */
+ bool has_gp_insn_p;
};
/* Information about a single argument. */
};
-/* Struct for recording constants. The meaning of the fields depends
- on a mips_constant_type:
-
- CONSTANT_NONE
- CONSTANT_GP
- No fields are valid.
-
- CONSTANT_SYMBOLIC
- SYMBOL is the referenced symbol and OFFSET is the constant offset.
-
- CONSTANT_RELOC
- SYMBOL and OFFSET are the same as for CONSTANT_SYMBOLIC. RELOC is
- the relocation number. */
-struct mips_constant_info
-{
- int reloc;
- rtx symbol;
- HOST_WIDE_INT offset;
-};
-
-
/* Information about an address described by mips_address_type.
- ADDRESS_INVALID
ADDRESS_CONST_INT
No fields are used.
ADDRESS_LO_SUM
REG is the register that contains the high part of the address,
- OFFSET is the symbolic address being referenced, and C contains
- the individual components of the symbolic address.
+ OFFSET is the symbolic address being referenced and SYMBOL_TYPE
+ is the type of OFFSET's symbol.
ADDRESS_SYMBOLIC
- C contains the symbol and offset. */
+ SYMBOL_TYPE is the type of symbol being referenced. */
+
struct mips_address_info
{
+ enum mips_address_type type;
rtx reg;
rtx offset;
- struct mips_constant_info c;
+ enum mips_symbol_type symbol_type;
};
static GTY (()) int mips_output_filename_first_time = 1;
+/* mips_split_p[X] is true if symbols of type X can be split by
+ mips_split_symbol(). */
+static bool mips_split_p[NUM_SYMBOL_TYPES];
+
+/* mips_lo_relocs[X] is the relocation to use when a symbol of type X
+ appears in a LO_SUM. It can be null if such LO_SUMs aren't valid or
+ if they are matched by a special .md file pattern. */
+static const char *mips_lo_relocs[NUM_SYMBOL_TYPES];
+
+/* Likewise for HIGHs. */
+static const char *mips_hi_relocs[NUM_SYMBOL_TYPES];
+
/* Hardware names for the registers. If -mrnames is used, this
will be overwritten with mips_sw_reg_names. */
#define TARGET_RTX_COSTS mips_rtx_costs
#undef TARGET_ADDRESS_COST
#define TARGET_ADDRESS_COST mips_address_cost
-#undef TARGET_DELEGITIMIZE_ADDRESS
-#define TARGET_DELEGITIMIZE_ADDRESS mips_delegitimize_address
#undef TARGET_ENCODE_SECTION_INFO
#define TARGET_ENCODE_SECTION_INFO mips_encode_section_info
struct gcc_target targetm = TARGET_INITIALIZER;
\f
-/* Return true if RELOC is a valid relocation number and OFFSET can be
- added to the relocation symbol.
-
- Note that OFFSET might not refer to part of the object. For example,
- in an expression like x[i - 0x12345], we might try to take the address
- of "x - 0x12345". */
-
-static bool
-mips_reloc_offset_ok_p (int reloc, HOST_WIDE_INT offset)
-{
- switch (reloc)
- {
- case RELOC_GOT_PAGE:
- /* The linker should provide enough page entries to cope with
- 16-bit offsets from a valid segment address. */
- return SMALL_OPERAND (offset);
-
- case RELOC_GOT_HI:
- case RELOC_GOT_LO:
- case RELOC_GOT_DISP:
- case RELOC_CALL16:
- case RELOC_CALL_HI:
- case RELOC_CALL_LO:
- case RELOC_LOADGP_HI:
- case RELOC_LOADGP_LO:
- /* These relocations should be applied to bare symbols only. */
- return offset == 0;
-
- default:
- return false;
- }
-}
-
-
-/* If X is one of the constants described by mips_constant_type,
- store its components in INFO and return its type. */
-
-static enum mips_constant_type
-mips_classify_constant (struct mips_constant_info *info, rtx x)
-{
- enum mips_constant_type type;
-
- type = CONSTANT_SYMBOLIC;
- info->offset = 0;
-
- if (GET_CODE (x) == CONST)
- {
- x = XEXP (x, 0);
-
- if (x == pic_offset_table_rtx)
- return CONSTANT_GP;
-
- while (GET_CODE (x) == PLUS && GET_CODE (XEXP (x, 1)) == CONST_INT)
- {
- info->offset += INTVAL (XEXP (x, 1));
- x = XEXP (x, 0);
- }
-
- if (TARGET_EXPLICIT_RELOCS
- && GET_CODE (x) == UNSPEC
- && mips_reloc_offset_ok_p (XINT (x, 1), info->offset))
- {
- info->reloc = XINT (x, 1);
- x = XVECEXP (x, 0, 0);
- type = CONSTANT_RELOC;
- }
- }
-
- if (GET_CODE (x) == SYMBOL_REF || GET_CODE (x) == LABEL_REF)
- {
- info->symbol = x;
- return type;
- }
- return CONSTANT_NONE;
-}
-
-
/* Classify symbol X, which must be a SYMBOL_REF or a LABEL_REF. */
static enum mips_symbol_type
}
+/* Split X into a base and a constant offset, storing them in *BASE
+ and *OFFSET respectively. */
+
+static void
+mips_split_const (rtx x, rtx *base, HOST_WIDE_INT *offset)
+{
+ *offset = 0;
+
+ if (GET_CODE (x) == CONST)
+ x = XEXP (x, 0);
+
+ if (GET_CODE (x) == PLUS && GET_CODE (XEXP (x, 1)) == CONST_INT)
+ {
+ *offset += INTVAL (XEXP (x, 1));
+ x = XEXP (x, 0);
+ }
+ *base = x;
+}
+
+
+/* Return true if X is a symbolic constant that can be calculated in
+ the same way as a bare symbol. If it is, store the type of the
+ symbol in *SYMBOL_TYPE. */
+
+static bool
+mips_symbolic_constant_p (rtx x, enum mips_symbol_type *symbol_type)
+{
+ HOST_WIDE_INT offset;
+
+ mips_split_const (x, &x, &offset);
+ if (UNSPEC_ADDRESS_P (x))
+ *symbol_type = UNSPEC_ADDRESS_TYPE (x);
+ else if (GET_CODE (x) == SYMBOL_REF || GET_CODE (x) == LABEL_REF)
+ *symbol_type = mips_classify_symbol (x);
+ else
+ return false;
+
+ if (offset == 0)
+ return true;
+
+ /* Check whether a nonzero offset is valid for the underlying
+ relocations. */
+ switch (*symbol_type)
+ {
+ case SYMBOL_GENERAL:
+ /* %hi() and %lo() can handle anything. */
+ return true;
+
+ case SYMBOL_SMALL_DATA:
+ /* Make sure that the offset refers to something within the
+ -G limit. If the offset is allowed to grow too much,
+ it could overflow the range of %gp_rel(). */
+ return (offset > 0 && offset < mips_section_threshold);
+
+ case SYMBOL_CONSTANT_POOL:
+ /* Similarly check the range of offsets for mips16 constant
+ pool entries. */
+ return (CONSTANT_POOL_ADDRESS_P (x)
+ && offset > 0
+ && offset < (int) GET_MODE_SIZE (get_pool_mode (x)));
+
+ case SYMBOL_GOT_LOCAL:
+ case SYMBOL_GOTOFF_PAGE:
+ /* The linker should provide enough local GOT entries for a
+ 16-bit offset. Larger offsets may lead to GOT overflow. */
+ return SMALL_OPERAND (offset);
+
+ case SYMBOL_GOT_GLOBAL:
+ case SYMBOL_GOTOFF_GLOBAL:
+ case SYMBOL_GOTOFF_CALL:
+ case SYMBOL_GOTOFF_LOADGP:
+ return false;
+ }
+ abort ();
+}
+
+
/* This function is used to implement REG_MODE_OK_FOR_BASE_P. */
int
}
-/* Return true if SYMBOL + OFFSET should be considered a legitimate
- address. LEA_P is true and MODE is word_mode if the address
- will be used in an LA or DLA macro. Otherwise MODE is the
- mode of the value being accessed.
-
- Some guiding principles:
-
- - Allow a nonzero offset when it takes no additional instructions.
- Ask for other offsets to be added separately.
-
- - Only allow multi-instruction load or store macros when MODE is
- word-sized or smaller. For other modes (including BLKmode)
- it is better to move the address into a register first. */
+/* Return true if symbols of type SYMBOL_TYPE can directly address a value
+ with mode MODE. This is used for both symbolic and LO_SUM addresses. */
static bool
-mips_symbolic_address_p (rtx symbol, HOST_WIDE_INT offset,
- enum machine_mode mode, int lea_p)
+mips_symbolic_address_p (enum mips_symbol_type symbol_type,
+ enum machine_mode mode)
{
- if (TARGET_EXPLICIT_RELOCS)
- return false;
-
- switch (mips_classify_symbol (symbol))
+ switch (symbol_type)
{
case SYMBOL_GENERAL:
- /* General symbols aren't valid addresses in mips16 code:
- they have to go into the constant pool. */
- return (!TARGET_MIPS16
- && !mips_split_addresses
- && SINGLE_WORD_MODE_P (mode));
+ return !TARGET_MIPS16;
case SYMBOL_SMALL_DATA:
- /* Small data references are normally OK for any address.
- But for mips16 code, we need to use a pseudo register
- instead of $gp as the base register. */
- return !TARGET_MIPS16;
+ return true;
case SYMBOL_CONSTANT_POOL:
- /* PC-relative addressing is only available for lw, sw, ld and sd.
- There's also a PC-relative add instruction. */
- return lea_p || GET_MODE_SIZE (mode) == 4 || GET_MODE_SIZE (mode) == 8;
+ /* PC-relative addressing is only available for lw, sw, ld and sd. */
+ return GET_MODE_SIZE (mode) == 4 || GET_MODE_SIZE (mode) == 8;
+
+ case SYMBOL_GOT_LOCAL:
+ return true;
case SYMBOL_GOT_GLOBAL:
- /* The address of the symbol is stored in the GOT. We can load
- it using an LA or DLA instruction, but any offset is added
- afterwards. */
- return lea_p && offset == 0;
+ /* The address will have to be loaded from the GOT first. */
+ return false;
- case SYMBOL_GOT_LOCAL:
- /* The symbol is part of a block of local memory. We fetch the
- address of the local memory from the GOT and then add the
- offset for this symbol. This addition can take the form of an
- offset(base) address, so the symbol is a legitimate address. */
- return SINGLE_WORD_MODE_P (mode);
+ case SYMBOL_GOTOFF_PAGE:
+ case SYMBOL_GOTOFF_GLOBAL:
+ case SYMBOL_GOTOFF_CALL:
+ case SYMBOL_GOTOFF_LOADGP:
+ return true;
}
abort ();
}
-/* If X is a valid address, describe it in INFO and return its type.
- STRICT says to only allow hard registers. MODE and LEA_P are
- the same as for mips_symbolic_address_p. */
+/* Return true if X is a valid address for machine mode MODE. If it is,
+ fill in INFO appropriately. STRICT is true if we should only accept
+ hard base registers. */
-static enum mips_address_type
+static bool
mips_classify_address (struct mips_address_info *info, rtx x,
- enum machine_mode mode, int strict, int lea_p)
+ enum machine_mode mode, int strict)
{
switch (GET_CODE (x))
{
case REG:
case SUBREG:
- if (mips_valid_base_register_p (x, mode, strict))
- {
- info->reg = x;
- info->offset = const0_rtx;
- return ADDRESS_REG;
- }
- return ADDRESS_INVALID;
+ info->type = ADDRESS_REG;
+ info->reg = x;
+ info->offset = const0_rtx;
+ return mips_valid_base_register_p (info->reg, mode, strict);
case PLUS:
- if (mips_valid_base_register_p (XEXP (x, 0), mode, strict)
- && const_arith_operand (XEXP (x, 1), VOIDmode))
- {
- info->reg = XEXP (x, 0);
- info->offset = XEXP (x, 1);
- return ADDRESS_REG;
- }
- return ADDRESS_INVALID;
+ info->type = ADDRESS_REG;
+ info->reg = XEXP (x, 0);
+ info->offset = XEXP (x, 1);
+ return (mips_valid_base_register_p (info->reg, mode, strict)
+ && const_arith_operand (info->offset, VOIDmode));
case LO_SUM:
- if (SINGLE_WORD_MODE_P (mode)
- && mips_valid_base_register_p (XEXP (x, 0), mode, strict)
- && (mips_classify_constant (&info->c, XEXP (x, 1))
- == CONSTANT_SYMBOLIC)
- && mips_splittable_symbol_p (mips_classify_symbol (info->c.symbol),
- info->c.offset))
- {
- info->reg = XEXP (x, 0);
- info->offset = XEXP (x, 1);
- return ADDRESS_LO_SUM;
- }
- return ADDRESS_INVALID;
+ info->type = ADDRESS_LO_SUM;
+ info->reg = XEXP (x, 0);
+ info->offset = XEXP (x, 1);
+ return (mips_valid_base_register_p (info->reg, mode, strict)
+ && mips_symbolic_constant_p (info->offset, &info->symbol_type)
+ && mips_symbolic_address_p (info->symbol_type, mode)
+ && mips_lo_relocs[info->symbol_type] != 0);
case CONST_INT:
/* Small-integer addresses don't occur very often, but they
are legitimate if $0 is a valid base register. */
- if (!TARGET_MIPS16 && SMALL_INT (x))
- return ADDRESS_CONST_INT;
- return ADDRESS_INVALID;
+ info->type = ADDRESS_CONST_INT;
+ return !TARGET_MIPS16 && SMALL_INT (x);
case CONST:
case LABEL_REF:
case SYMBOL_REF:
- if (mips_classify_constant (&info->c, x) == CONSTANT_SYMBOLIC
- && mips_symbolic_address_p (info->c.symbol, info->c.offset,
- mode, lea_p))
- return ADDRESS_SYMBOLIC;
- return ADDRESS_INVALID;
-
- default:
- return ADDRESS_INVALID;
- }
-}
-\f
-/* Return true if symbols of the given type can be split into a
- high part and a LO_SUM. In the case of small data symbols,
- the high part will be $gp. */
-
-static bool
-mips_splittable_symbol_p (enum mips_symbol_type type, HOST_WIDE_INT offset)
-{
- switch (type)
- {
- case SYMBOL_GENERAL:
- return TARGET_EXPLICIT_RELOCS || mips_split_addresses;
-
- case SYMBOL_GOT_LOCAL:
- return TARGET_EXPLICIT_RELOCS && SMALL_OPERAND (offset);
-
- case SYMBOL_SMALL_DATA:
- return ((TARGET_EXPLICIT_RELOCS || TARGET_MIPS16)
- && (offset == 0
- || (offset > 0 && offset <= mips_section_threshold)));
+ info->type = ADDRESS_SYMBOLIC;
+ return (mips_symbolic_constant_p (x, &info->symbol_type)
+ && mips_symbolic_address_p (info->symbol_type, mode)
+ && !mips_split_p[info->symbol_type]);
default:
return false;
}
}
-
-
+\f
/* Return the number of instructions needed to load a symbol of the
given type into a register. If valid in an address, the same number
of instructions are needed for loads and stores. Treat extended
switch (type)
{
case SYMBOL_GENERAL:
+ /* In mips16 code, general symbols must be fetched from the
+ constant pool. */
+ if (TARGET_MIPS16)
+ return 0;
+
/* When using 64-bit symbols, we need 5 preparatory instructions,
such as:
extended instruction. */
return 2;
- case SYMBOL_GOT_GLOBAL:
- /* When using a small GOT, we just fetch the address using
- a gp-relative load. For a big GOT, we need a sequence
- such as:
-
- lui $at,%got_hi(symbol)
- daddu $at,$at,$gp
-
- and the final address is $at + %got_lo(symbol). */
- return (TARGET_XGOT ? 3 : 1);
-
case SYMBOL_GOT_LOCAL:
/* For o32 and o64, the sequence is:
of GAS insert a nop in the n32/n64 sequences too so, for
simplicity, use the worst case of 3 instructions. */
return 3;
+
+ case SYMBOL_GOT_GLOBAL:
+ /* When using a small GOT, we just fetch the address using
+ a gp-relative load. For a big GOT, we need a sequence
+ such as:
+
+ lui $at,%got_hi(symbol)
+ daddu $at,$at,$gp
+
+ and the final address is $at + %got_lo(symbol). */
+ return (TARGET_XGOT ? 3 : 1);
+
+ case SYMBOL_GOTOFF_PAGE:
+ case SYMBOL_GOTOFF_GLOBAL:
+ case SYMBOL_GOTOFF_CALL:
+ case SYMBOL_GOTOFF_LOADGP:
+ /* Check whether the offset is a 16- or 32-bit value. */
+ return mips_split_p[type] ? 2 : 1;
}
abort ();
}
/* Each word of a multi-word value will be accessed individually. */
factor = (GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD;
- switch (mips_classify_address (&addr, x, mode, 0, 0))
- {
- case ADDRESS_INVALID:
- return 0;
-
- case ADDRESS_REG:
- if (TARGET_MIPS16
- && !mips16_unextended_reference_p (mode, addr.reg, addr.offset))
- return factor * 2;
- return factor;
+ if (mips_classify_address (&addr, x, mode, false))
+ switch (addr.type)
+ {
+ case ADDRESS_REG:
+ if (TARGET_MIPS16
+ && !mips16_unextended_reference_p (mode, addr.reg, addr.offset))
+ return factor * 2;
+ return factor;
- case ADDRESS_LO_SUM:
- return (TARGET_MIPS16 ? factor * 2 : factor);
+ case ADDRESS_LO_SUM:
+ return (TARGET_MIPS16 ? factor * 2 : factor);
- case ADDRESS_CONST_INT:
- return factor;
+ case ADDRESS_CONST_INT:
+ return factor;
- case ADDRESS_SYMBOLIC:
- return factor * mips_symbol_insns (mips_classify_symbol (addr.c.symbol));
- }
- abort ();
+ case ADDRESS_SYMBOLIC:
+ return factor * mips_symbol_insns (addr.symbol_type);
+ }
+ return 0;
}
int
mips_const_insns (rtx x)
{
- struct mips_constant_info c;
struct mips_integer_op codes[MIPS_MAX_INTEGER_OPS];
+ enum mips_symbol_type symbol_type;
+ HOST_WIDE_INT offset;
switch (GET_CODE (x))
{
case CONSTANT_P_RTX:
+ return 1;
+
case HIGH:
+ if (TARGET_MIPS16
+ || !mips_symbolic_constant_p (XEXP (x, 0), &symbol_type)
+ || !mips_split_p[symbol_type])
+ return 0;
+
return 1;
case CONST_INT:
case CONST_DOUBLE:
return (!TARGET_MIPS16 && x == CONST0_RTX (GET_MODE (x)) ? 1 : 0);
- default:
- switch (mips_classify_constant (&c, x))
+ case CONST:
+ if (CONST_GP_P (x))
+ return 1;
+
+ /* See if we can refer to X directly. */
+ if (mips_symbolic_constant_p (x, &symbol_type))
+ return mips_symbol_insns (symbol_type);
+
+ /* Otherwise try splitting the constant into a base and offset.
+ 16-bit offsets can be added using an extra addiu. Larger offsets
+ must be calculated separately and then added to the base. */
+ mips_split_const (x, &x, &offset);
+ if (offset != 0)
{
- case CONSTANT_NONE:
- return 0;
+ int n = mips_const_insns (x);
+ if (n != 0)
+ {
+ if (SMALL_OPERAND (offset))
+ return n + 1;
+ else
+ return n + 1 + mips_build_integer (codes, offset);
+ }
+ }
+ return 0;
- case CONSTANT_GP:
- case CONSTANT_RELOC:
- return 1;
+ case SYMBOL_REF:
+ case LABEL_REF:
+ return mips_symbol_insns (mips_classify_symbol (x));
- case CONSTANT_SYMBOLIC:
- return mips_symbol_insns (mips_classify_symbol (c.symbol));
- }
- abort ();
+ default:
+ return 0;
}
}
}
-/* Return true if OP is a symbolic constant that refers to a
- global PIC symbol. */
-
-bool
-mips_global_pic_constant_p (rtx op)
-{
- struct mips_constant_info c;
-
- return (mips_classify_constant (&c, op) == CONSTANT_SYMBOLIC
- && mips_classify_symbol (c.symbol) == SYMBOL_GOT_GLOBAL);
-}
-
-
/* Return truth value of whether OP can be used as an operands
where a register or 16 bit unsigned integer is needed. */
int
const_arith_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
{
- struct mips_constant_info c;
-
- return ((GET_CODE (op) == CONST_INT && SMALL_INT (op))
- || mips_classify_constant (&c, op) == CONSTANT_RELOC);
+ return GET_CODE (op) == CONST_INT && SMALL_INT (op);
}
int
call_insn_operand (rtx op, enum machine_mode mode)
{
- struct mips_constant_info c;
+ enum mips_symbol_type symbol_type;
- if (mips_classify_constant (&c, op) == CONSTANT_SYMBOLIC)
- switch (mips_classify_symbol (c.symbol))
+ if (mips_symbolic_constant_p (op, &symbol_type))
+ switch (symbol_type)
{
case SYMBOL_GENERAL:
/* If -mlong-calls, force all calls to use register addressing. */
Using "la $25,foo; jal $25" would prevent the lazy binding
of "foo", so keep the address of global symbols with the
jal macro. */
- return c.offset == 0 && !TARGET_EXPLICIT_RELOCS;
+ return !TARGET_EXPLICIT_RELOCS;
default:
return false;
int
move_operand (rtx op, enum machine_mode mode)
{
- struct mips_constant_info c;
+ enum mips_symbol_type symbol_type;
- if (GET_CODE (op) == HIGH && TARGET_ABICALLS)
+ if (!general_operand (op, mode))
return false;
- if (GET_CODE (op) == CONST_INT && !TARGET_MIPS16)
- return (SMALL_INT (op) || SMALL_INT_UNSIGNED (op) || LUI_INT (op));
- if (mips_classify_constant (&c, op) == CONSTANT_SYMBOLIC)
- return mips_symbolic_address_p (c.symbol, c.offset, word_mode, 1);
- return general_operand (op, mode);
+
+ switch (GET_CODE (op))
+ {
+ case CONST_INT:
+ /* When generating mips16 code, LEGITIMATE_CONSTANT_P rejects
+ CONST_INTs that can't be loaded using simple insns. */
+ if (TARGET_MIPS16)
+ return true;
+
+ /* Otherwise check whether the constant can be loaded in a single
+ instruction. */
+ return LUI_INT (op) || SMALL_INT (op) || SMALL_INT_UNSIGNED (op);
+
+ case CONST:
+ case SYMBOL_REF:
+ case LABEL_REF:
+ if (CONST_GP_P (op))
+ return true;
+
+ return (mips_symbolic_constant_p (op, &symbol_type)
+ && !mips_split_p[symbol_type]);
+
+ default:
+ return true;
+ }
}
int
symbolic_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
{
- struct mips_constant_info c;
+ enum mips_symbol_type symbol_type;
+
+ return mips_symbolic_constant_p (op, &symbol_type);
+}
+
+
+/* Return true if we're generating PIC and OP is a global symbol. */
+
+int
+global_got_operand (rtx op, enum machine_mode mode)
+{
+ enum mips_symbol_type symbol_type;
+
+ return ((mode == VOIDmode || mode == GET_MODE (op))
+ && mips_symbolic_constant_p (op, &symbol_type)
+ && symbol_type == SYMBOL_GOT_GLOBAL);
+}
+
+
+/* Likewise for local symbols. */
+
+int
+local_got_operand (rtx op, enum machine_mode mode)
+{
+ enum mips_symbol_type symbol_type;
- return mips_classify_constant (&c, op) == CONSTANT_SYMBOLIC;
+ return ((mode == VOIDmode || mode == GET_MODE (op))
+ && mips_symbolic_constant_p (op, &symbol_type)
+ && symbol_type == SYMBOL_GOT_LOCAL);
}
return ((mode == VOIDmode || mode == GET_MODE (op))
&& GET_CODE (op) == MEM
- && mips_classify_address (&addr, XEXP (op, 0),
- GET_MODE (op), false, true) == ADDRESS_REG
+ && mips_classify_address (&addr, XEXP (op, 0), GET_MODE (op), false)
+ && addr.type == ADDRESS_REG
&& addr.reg == stack_pointer_rtx);
}
{
struct mips_address_info addr;
- return mips_classify_address (&addr, x, mode, strict, 0) != ADDRESS_INVALID;
+ return mips_classify_address (&addr, x, mode, strict);
}
-/* Return an rtx that represents the effect of applying relocation
- RELOC to symbolic address ADDR. */
+/* Copy VALUE to a register and return that register. If new psuedos
+ are allowed, copy it into a new register, otherwise use DEST. */
static rtx
-mips_reloc (rtx addr, int reloc)
+mips_force_temporary (rtx dest, rtx value)
{
- struct mips_constant_info c;
- rtx x;
-
- if (mips_classify_constant (&c, addr) != CONSTANT_SYMBOLIC)
- abort ();
-
- x = gen_rtx_UNSPEC (VOIDmode, gen_rtvec (1, c.symbol), reloc);
- return plus_constant (gen_rtx_CONST (VOIDmode, x), c.offset);
+ if (!no_new_pseudos)
+ return force_reg (Pmode, value);
+ else
+ {
+ emit_move_insn (copy_rtx (dest), value);
+ return dest;
+ }
}
-/* Likewise, but shift the result left 16 bits. The expression can be
- used as the right hand side of an LUISI or LUIDI pattern. */
+/* Return a LO_SUM expression for ADDR. TEMP is as for mips_force_temporary
+ and is used to load the high part into a register. */
static rtx
-mips_lui_reloc (rtx addr, int reloc)
+mips_split_symbol (rtx temp, rtx addr)
{
- return gen_rtx_UNSPEC (Pmode,
- gen_rtvec (1, mips_reloc (addr, reloc)),
- UNSPEC_HIGH);
+ rtx high;
+
+ if (TARGET_MIPS16)
+ high = mips16_gp_pseudo_reg ();
+ else
+ high = mips_force_temporary (temp, gen_rtx_HIGH (Pmode, copy_rtx (addr)));
+ return gen_rtx_LO_SUM (Pmode, high, addr);
}
-/* Copy VALUE to a register and return that register. Use DEST as the
- register if non-null, otherwise create a new one.
- VALUE must be valid on the right hand side of a simple SET pattern.
- The operation happens in Pmode. */
+/* Return an UNSPEC address with underlying address ADDRESS and symbol
+ type SYMBOL_TYPE. */
static rtx
-mips_force_temporary (rtx dest, rtx value)
+mips_unspec_address (rtx address, enum mips_symbol_type symbol_type)
{
- if (dest == 0)
- return force_reg (Pmode, value);
- else
- {
- if (!rtx_equal_p (dest, value))
- emit_insn (gen_rtx_SET (VOIDmode, copy_rtx (dest), value));
- return dest;
- }
+ rtx base;
+ HOST_WIDE_INT offset;
+
+ mips_split_const (address, &base, &offset);
+ base = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, base),
+ UNSPEC_ADDRESS_FIRST + symbol_type);
+ return plus_constant (gen_rtx_CONST (Pmode, base), offset);
}
-/* Return a legitimate address for REG + OFFSET. This function will
- create a temporary register if OFFSET is not a SMALL_OPERAND. */
+/* If mips_unspec_address (ADDR, SYMBOL_TYPE) is a 32-bit value, add the
+ high part to BASE and return the result. Just return BASE otherwise.
+
+ The returned expression can be used as the first operand to a LO_SUM. */
static rtx
-mips_add_offset (rtx reg, HOST_WIDE_INT offset)
+mips_unspec_offset_high (rtx base, rtx addr, enum mips_symbol_type symbol_type)
{
- if (!SMALL_OPERAND (offset))
- reg = expand_simple_binop (GET_MODE (reg), PLUS,
- GEN_INT (CONST_HIGH_PART (offset)),
- reg, NULL, 0, OPTAB_WIDEN);
-
- return plus_constant (reg, CONST_LOW_PART (offset));
+ if (mips_split_p[symbol_type])
+ {
+ addr = gen_rtx_HIGH (Pmode, mips_unspec_address (addr, symbol_type));
+ base = force_reg (Pmode, expand_simple_binop (Pmode, PLUS, base, addr,
+ NULL, 0, OPTAB_WIDEN));
+ }
+ return base;
}
-/* Return the GOT entry whose address is given by %RELOC(ADDR)(BASE).
- BASE is a base register (such as $gp), ADDR is addresses being
- sought and RELOC is the relocation that should be used. */
+/* Return a memory reference for the GOT slot whose offset is given by
+ mips_unspec_address (ADDR, SYMBOL_TYPE). Register BASE contains the
+ high part of the offset plus $gp. */
static rtx
-mips_load_got (rtx base, rtx addr, int reloc)
+mips_load_got (rtx base, rtx addr, enum mips_symbol_type symbol_type)
{
- rtx mem;
+ rtx mem, offset;
- mem = gen_rtx_MEM (ptr_mode,
- gen_rtx_PLUS (Pmode, base, mips_reloc (addr, reloc)));
+ offset = mips_unspec_address (addr, symbol_type);
+ mem = gen_rtx_MEM (ptr_mode, gen_rtx_LO_SUM (Pmode, base, offset));
set_mem_alias_set (mem, mips_got_alias_set);
/* GOT references can't trap. */
/* If we allow a function's address to be lazily bound, its entry
may change after the first call. Other entries are constant. */
- if (reloc != RELOC_CALL16 && reloc != RELOC_CALL_LO)
+ if (symbol_type != SYMBOL_GOTOFF_CALL)
RTX_UNCHANGING_P (mem) = 1;
return mem;
}
-/* Obtain the address of ADDR from the GOT using relocation RELOC.
- The returned address may be used on the right hand side of a SET. */
+/* Return the offset of ADDR's GOT entry from _gp. ADDR is a
+ global_got_operand. */
-static rtx
-mips_load_got16 (rtx addr, int reloc)
+rtx
+mips_gotoff_global (rtx addr)
{
- return mips_load_got (pic_offset_table_rtx, addr, reloc);
+ return mips_unspec_address (addr, SYMBOL_GOTOFF_GLOBAL);
}
-/* Like mips_load_got16, but for 32-bit offsets. HIGH_RELOC is the
- relocation that gives the high 16 bits of the offset and LOW_RELOC is
- the relocation that gives the low 16 bits. TEMP is a Pmode register
- to use a temporary, or null if new registers can be created at will. */
+/* Fetch the high part of local_got_operand ADDR from the GOT. */
-static rtx
-mips_load_got32 (rtx temp, rtx addr, int high_reloc, int low_reloc)
+rtx
+mips_load_got_page (rtx addr)
{
- rtx x;
-
- x = mips_force_temporary (temp, mips_lui_reloc (addr, high_reloc));
- x = mips_force_temporary (temp,
- gen_rtx_PLUS (Pmode, pic_offset_table_rtx, x));
- return mips_load_got (x, addr, low_reloc);
+ return mips_load_got (pic_offset_table_rtx, addr, SYMBOL_GOTOFF_PAGE);
}
-/* Copy the high part of ADDR into a register and return the register.
- Use DEST as the register if non-null. */
+/* Fetch the address of global_got_operand ADDR from the GOT. BASE is a
+ register that holds the address _gp + %got_hi(ADDR). */
-static rtx
-mips_emit_high (rtx dest, rtx addr)
+rtx
+mips_load_got_global (rtx base, rtx addr)
{
- rtx high, x;
-
- high = gen_rtx_HIGH (Pmode, addr);
- if (TARGET_ABICALLS)
- {
- x = mips_load_got16 (copy_rtx (addr), RELOC_GOT_PAGE);
- x = mips_force_temporary (dest, x);
- set_unique_reg_note (get_last_insn (), REG_EQUAL, high);
- }
- else
- x = mips_force_temporary (dest, high);
-
- return x;
+ return mips_load_got (base, addr, SYMBOL_GOTOFF_GLOBAL);
}
-/* See if *XLOC is a symbolic constant that can be reduced in some way.
- If it is, set *XLOC to the reduced expression and return true.
- The new expression will be both a legitimate address and a legitimate
- source operand for a mips.md SET pattern. If OFFSETABLE_P, the
- address will be offsetable.
-
- DEST is a register to use a temporary, or null if new registers
- can be created at will. */
+/* Return a legitimate address for REG + OFFSET. This function will
+ create a temporary register if OFFSET is not a SMALL_OPERAND. */
-static bool
-mips_legitimize_symbol (rtx dest, rtx *xloc, int offsetable_p)
+static rtx
+mips_add_offset (rtx reg, HOST_WIDE_INT offset)
{
- struct mips_constant_info c;
- enum mips_symbol_type symbol_type;
- rtx x;
-
- if (mips_classify_constant (&c, *xloc) != CONSTANT_SYMBOLIC)
- return false;
-
- symbol_type = mips_classify_symbol (c.symbol);
-
- /* If a non-offsetable address is OK, try splitting it into a
- high part and a LO_SUM. */
- if (!offsetable_p && mips_splittable_symbol_p (symbol_type, c.offset))
- {
- if (symbol_type == SYMBOL_SMALL_DATA)
- x = mips_sdata_pointer ();
- else
- x = mips_emit_high (dest, *xloc);
- if (x != 0)
- {
- *xloc = gen_rtx_LO_SUM (Pmode, x, copy_rtx (*xloc));
- return true;
- }
- }
-
- /* If the offset is nonzero, move the symbol into a register (always valid)
- and add the constant in afterwards. This requires an extra temporary if
- the offset isn't a signed 16-bit number.
-
- For mips16, it's better to force the constant into memory instead. */
- if (!TARGET_MIPS16
- && c.offset != 0
- && (SMALL_OPERAND (c.offset) || dest == 0))
- {
- x = (dest == 0 ? gen_reg_rtx (Pmode) : dest);
- emit_move_insn (copy_rtx (x), c.symbol);
- *xloc = mips_add_offset (x, c.offset);
- return true;
- }
+ if (!SMALL_OPERAND (offset))
+ reg = expand_simple_binop (GET_MODE (reg), PLUS,
+ GEN_INT (CONST_HIGH_PART (offset)),
+ reg, NULL, 0, OPTAB_WIDEN);
- return false;
+ return plus_constant (reg, CONST_LOW_PART (offset));
}
bool
mips_legitimize_address (rtx *xloc, enum machine_mode mode)
{
- if (mips_legitimize_symbol (0, xloc, !SINGLE_WORD_MODE_P (mode)))
- return true;
+ enum mips_symbol_type symbol_type;
+
+ /* See if the address can split into a high part and a LO_SUM. */
+ if (mips_symbolic_constant_p (*xloc, &symbol_type)
+ && mips_symbolic_address_p (symbol_type, mode)
+ && mips_split_p[symbol_type])
+ {
+ *xloc = mips_split_symbol (0, *xloc);
+ return true;
+ }
if (GET_CODE (*xloc) == PLUS && GET_CODE (XEXP (*xloc, 1)) == CONST_INT)
{
static void
mips_legitimize_const_move (enum machine_mode mode, rtx dest, rtx src)
{
- rtx temp;
-
- temp = no_new_pseudos ? dest : 0;
-
- /* If generating PIC, the high part of an address is loaded from the GOT. */
- if (GET_CODE (src) == HIGH)
- {
- mips_emit_high (dest, XEXP (src, 0));
- return;
- }
+ rtx base;
+ HOST_WIDE_INT offset;
+ enum mips_symbol_type symbol_type;
+ /* Split moves of big integers into smaller pieces. In mips16 code,
+ it's better to force the constant into memory instead. */
if (GET_CODE (src) == CONST_INT && !TARGET_MIPS16)
{
mips_move_integer (dest, INTVAL (src));
return;
}
- /* Fetch global symbols from the GOT. */
- if (TARGET_EXPLICIT_RELOCS
- && GET_CODE (src) == SYMBOL_REF
- && mips_classify_symbol (src) == SYMBOL_GOT_GLOBAL)
+ /* See if the symbol can be split. For mips16, this is often worse than
+ forcing it in the constant pool since it needs the single-register form
+ of addiu or daddiu. */
+ if (!TARGET_MIPS16
+ && mips_symbolic_constant_p (src, &symbol_type)
+ && mips_split_p[symbol_type])
{
- if (TARGET_XGOT)
- src = mips_load_got32 (temp, src, RELOC_GOT_HI, RELOC_GOT_LO);
- else
- src = mips_load_got16 (src, RELOC_GOT_DISP);
- emit_insn (gen_rtx_SET (VOIDmode, dest, src));
+ emit_move_insn (dest, mips_split_symbol (dest, src));
return;
}
- /* Try handling the source operand as a symbolic address. */
- if (mips_legitimize_symbol (temp, &src, false))
+ /* If we have (const (plus symbol offset)), load the symbol first
+ and then add in the offset. This is usually better than forcing
+ the constant into memory, at least in non-mips16 code. */
+ mips_split_const (src, &base, &offset);
+ if (!TARGET_MIPS16
+ && offset != 0
+ && (!no_new_pseudos || SMALL_OPERAND (offset)))
{
- emit_insn (gen_rtx_SET (VOIDmode, dest, src));
+ base = mips_force_temporary (dest, base);
+ emit_move_insn (dest, mips_add_offset (base, offset));
return;
}
src = force_const_mem (mode, src);
/* When using explicit relocs, constant pool references are sometimes
- not legitimate addresses. mips_legitimize_symbol must be able to
- deal with all such cases. */
- if (GET_CODE (src) == MEM && !memory_operand (src, VOIDmode))
- {
- src = copy_rtx (src);
- if (!mips_legitimize_symbol (temp, &XEXP (src, 0), false))
- abort ();
- }
+ not legitimate addresses. */
+ if (!memory_operand (src, VOIDmode))
+ src = replace_equiv_address (src, mips_split_symbol (dest, XEXP (src, 0)));
emit_move_insn (dest, src);
}
}
return false;
}
-
-
-/* Convert GOT and GP-relative accesses back into their original form.
- Used by both TARGET_DELEGITIMIZE_ADDRESS and FIND_BASE_TERM. */
-
-rtx
-mips_delegitimize_address (rtx x)
-{
- struct mips_constant_info c;
-
- if (GET_CODE (x) == MEM
- && GET_CODE (XEXP (x, 0)) == PLUS
- && mips_classify_constant (&c, XEXP (XEXP (x, 0), 1)) == CONSTANT_RELOC
- && mips_classify_symbol (c.symbol) == SYMBOL_GOT_GLOBAL)
- return c.symbol;
-
- if (GET_CODE (x) == LO_SUM
- && XEXP (x, 0) == (TARGET_MIPS16
- ? cfun->machine->mips16_gp_pseudo_rtx
- : pic_offset_table_rtx))
- return XEXP (x, 1);
-
- return x;
-}
\f
/* We need a lot of little routines to check constant values on the
mips16. These are used to figure out how long the instruction will
case SYMBOL_REF:
case LABEL_REF:
case CONST_DOUBLE:
- if (((outer_code) == PLUS || (outer_code) == MINUS)
- && const_arith_operand (x, VOIDmode))
- {
- *total = 0;
- return true;
- }
+ if (LEGITIMATE_CONSTANT_P (x))
+ {
+ *total = COSTS_N_INSNS (1);
+ return true;
+ }
else
- {
- int n = mips_const_insns (x);
- return (n == 0 ? CONSTANT_POOL_COST : COSTS_N_INSNS (n));
- }
+ {
+ /* The value will need to be fetched from the constant pool. */
+ *total = CONSTANT_POOL_COST;
+ return true;
+ }
case MEM:
{
}
if (GET_CODE (op) == MEM)
- return adjust_address (op, word_mode, byte);
+ return mips_rewrite_small_data (adjust_address (op, word_mode, byte));
return simplify_gen_subreg (word_mode, op, mode, byte);
}
mips_output_move (rtx dest, rtx src)
{
enum rtx_code dest_code, src_code;
- struct mips_constant_info c;
bool dbl_p;
dest_code = GET_CODE (dest);
if (src_code == HIGH)
return "lui\t%0,%h1";
- switch (mips_classify_constant (&c, src))
- {
- case CONSTANT_NONE:
- break;
-
- case CONSTANT_GP:
- return "move\t%0,%1";
-
- case CONSTANT_RELOC:
- return "li\t%0,%1";
+ if (CONST_GP_P (src))
+ return "move\t%0,%1";
- case CONSTANT_SYMBOLIC:
- return (dbl_p ? "dla\t%0,%a1" : "la\t%0,%a1");
- }
+ if (symbolic_operand (src, VOIDmode))
+ return (dbl_p ? "dla\t%0,%1" : "la\t%0,%1");
}
if (src_code == REG && FP_REG_P (REGNO (src)))
{
{
if (!call_insn_operand (addr, VOIDmode))
{
- /* When generating PIC, try to allow global functions to be
- lazily bound. */
- if (TARGET_EXPLICIT_RELOCS
- && GET_CODE (addr) == SYMBOL_REF
- && mips_classify_symbol (addr) == SYMBOL_GOT_GLOBAL)
+ if (TARGET_EXPLICIT_RELOCS && global_got_operand (addr, VOIDmode))
{
- if (TARGET_XGOT)
- addr = mips_load_got32 (0, addr, RELOC_CALL_HI, RELOC_CALL_LO);
- else
- addr = mips_load_got16 (addr, RELOC_CALL16);
+ rtx high = mips_unspec_offset_high (pic_offset_table_rtx,
+ addr, SYMBOL_GOTOFF_CALL);
+ addr = mips_load_got (high, addr, SYMBOL_GOTOFF_CALL);
}
addr = force_reg (Pmode, addr);
}
/* Create a unique alias set for GOT references. */
mips_got_alias_set = new_alias_set ();
+
+ if (TARGET_EXPLICIT_RELOCS || mips_split_addresses)
+ {
+ mips_split_p[SYMBOL_GENERAL] = true;
+ mips_hi_relocs[SYMBOL_GENERAL] = "%hi(";
+ mips_lo_relocs[SYMBOL_GENERAL] = "%lo(";
+ }
+
+ if (TARGET_MIPS16)
+ {
+ /* The high part is provided by a pseudo copy of $gp. */
+ mips_split_p[SYMBOL_SMALL_DATA] = true;
+ mips_lo_relocs[SYMBOL_SMALL_DATA] = "%gprel(";
+ }
+
+ if (TARGET_EXPLICIT_RELOCS)
+ {
+ /* Small data constants are kept whole until after reload,
+ then lowered by mips_rewrite_small_data. */
+ mips_lo_relocs[SYMBOL_SMALL_DATA] = "%gp_rel(";
+
+ mips_split_p[SYMBOL_GOT_LOCAL] = true;
+ if (TARGET_NEWABI)
+ {
+ mips_lo_relocs[SYMBOL_GOTOFF_PAGE] = "%got_page(";
+ mips_lo_relocs[SYMBOL_GOT_LOCAL] = "%got_ofst(";
+ }
+ else
+ {
+ mips_lo_relocs[SYMBOL_GOTOFF_PAGE] = "%got(";
+ mips_lo_relocs[SYMBOL_GOT_LOCAL] = "%lo(";
+ }
+
+ if (TARGET_XGOT)
+ {
+ /* The HIGH and LO_SUM are matched by special .md patterns. */
+ mips_split_p[SYMBOL_GOT_GLOBAL] = true;
+
+ mips_split_p[SYMBOL_GOTOFF_GLOBAL] = true;
+ mips_hi_relocs[SYMBOL_GOTOFF_GLOBAL] = "%got_hi(";
+ mips_lo_relocs[SYMBOL_GOTOFF_GLOBAL] = "%got_lo(";
+
+ mips_split_p[SYMBOL_GOTOFF_CALL] = true;
+ mips_hi_relocs[SYMBOL_GOTOFF_CALL] = "%call_hi(";
+ mips_lo_relocs[SYMBOL_GOTOFF_CALL] = "%call_lo(";
+ }
+ else
+ {
+ if (TARGET_NEWABI)
+ mips_lo_relocs[SYMBOL_GOTOFF_GLOBAL] = "%got_disp(";
+ else
+ mips_lo_relocs[SYMBOL_GOTOFF_GLOBAL] = "%got(";
+ mips_lo_relocs[SYMBOL_GOTOFF_CALL] = "%call16(";
+ }
+ }
+
+ if (TARGET_NEWABI)
+ {
+ mips_split_p[SYMBOL_GOTOFF_LOADGP] = true;
+ mips_hi_relocs[SYMBOL_GOTOFF_LOADGP] = "%hi(%neg(%gp_rel(";
+ mips_lo_relocs[SYMBOL_GOTOFF_LOADGP] = "%lo(%neg(%gp_rel(";
+ }
}
/* Implement CONDITIONAL_REGISTER_USAGE. */
print_operand (FILE *file, rtx op, int letter)
{
register enum rtx_code code;
- struct mips_constant_info c;
- const char *reloc;
if (PRINT_OPERAND_PUNCT_VALID_P (letter))
{
code = GET_CODE (op);
- if (letter == 'h')
- {
- if (GET_CODE (op) != HIGH)
- abort ();
- fputs ("%hi(", file);
- output_addr_const (file, XEXP (op, 0));
- fputc (')', file);
- }
-
- else if (letter == 'C')
+ if (letter == 'C')
switch (code)
{
case EQ: fputs ("eq", file); break;
fatal_insn ("PRINT_OPERAND, invalid insn for %%W", op);
}
+ else if (letter == 'h')
+ {
+ if (GET_CODE (op) == HIGH)
+ op = XEXP (op, 0);
+
+ print_operand_reloc (file, op, mips_hi_relocs);
+ }
+
+ else if (letter == 'R')
+ print_operand_reloc (file, op, mips_lo_relocs);
+
else if (letter == 'S')
{
char buffer[100];
else if (letter == 't')
fputs (code == EQ ? "t" : "f", file);
- else
- switch (mips_classify_constant (&c, op))
- {
- case CONSTANT_SYMBOLIC:
- if (letter == 'R')
- {
- if (mips_classify_symbol (c.symbol) == SYMBOL_SMALL_DATA)
- fputs (TARGET_MIPS16 ? "%gprel(" : "%gp_rel(", file);
- else if (TARGET_ABICALLS && TARGET_NEWABI)
- fputs ("%got_ofst(", file);
- else
- fputs ("%lo(", file);
- output_addr_const (file, op);
- fputc (')', file);
- break;
- }
- /* ... fall through ... */
+ else if (CONST_GP_P (op))
+ print_operand (file, XEXP (op, 0), letter);
- case CONSTANT_NONE:
- output_addr_const (file, op);
- break;
+ else
+ output_addr_const (file, op);
+}
- case CONSTANT_GP:
- fputs (reg_names[PIC_OFFSET_TABLE_REGNUM], file);
- break;
- case CONSTANT_RELOC:
- reloc = mips_reloc_string (c.reloc);
- fputs (reloc, file);
- output_addr_const (file, plus_constant (c.symbol, c.offset));
- while (*reloc != 0)
- if (*reloc++ == '(')
- fputc (')', file);
- }
-}
-\f
-/* Return the assembly operator used for the given type of relocation. */
+/* Print symbolic operand OP, which is part of a HIGH or LO_SUM.
+ RELOCS is the array of relocations to use. */
-static const char *
-mips_reloc_string (int reloc)
+static void
+print_operand_reloc (FILE *file, rtx op, const char **relocs)
{
- switch (reloc)
- {
- case RELOC_GOT_HI: return "%got_hi(";
- case RELOC_GOT_LO: return "%got_lo(";
- case RELOC_GOT_PAGE: return (TARGET_NEWABI ? "%got_page(" : "%got(");
- case RELOC_GOT_DISP: return (TARGET_NEWABI ? "%got_disp(" : "%got(");
- case RELOC_CALL16: return "%call16(";
- case RELOC_CALL_HI: return "%call_hi(";
- case RELOC_CALL_LO: return "%call_lo(";
- case RELOC_LOADGP_HI: return "%hi(%neg(%gp_rel(";
- case RELOC_LOADGP_LO: return "%lo(%neg(%gp_rel(";
- }
- abort ();
-}
+ enum mips_symbol_type symbol_type;
+ const char *p;
+ rtx base;
+ HOST_WIDE_INT offset;
+
+ if (!mips_symbolic_constant_p (op, &symbol_type) || relocs[symbol_type] == 0)
+ fatal_insn ("PRINT_OPERAND, invalid operand for relocation", op);
+ /* If OP uses an UNSPEC address, we want to print the inner symbol. */
+ mips_split_const (op, &base, &offset);
+ if (UNSPEC_ADDRESS_P (base))
+ op = plus_constant (UNSPEC_ADDRESS (base), offset);
+
+ fputs (relocs[symbol_type], file);
+ output_addr_const (file, op);
+ for (p = relocs[symbol_type]; *p != 0; p++)
+ if (*p == '(')
+ fputc (')', file);
+}
+\f
/* Output address operand X to FILE. */
void
{
struct mips_address_info addr;
- switch (mips_classify_address (&addr, x, word_mode, 1, 1))
- {
- case ADDRESS_INVALID:
- abort ();
-
- case ADDRESS_REG:
- print_operand (file, addr.offset, 0);
- fprintf (file, "(%s)", reg_names[REGNO (addr.reg)]);
- return;
+ if (mips_classify_address (&addr, x, word_mode, true))
+ switch (addr.type)
+ {
+ case ADDRESS_REG:
+ print_operand (file, addr.offset, 0);
+ fprintf (file, "(%s)", reg_names[REGNO (addr.reg)]);
+ return;
- case ADDRESS_LO_SUM:
- print_operand (file, addr.offset, 'R');
- fprintf (file, "(%s)", reg_names[REGNO (addr.reg)]);
- return;
+ case ADDRESS_LO_SUM:
+ print_operand (file, addr.offset, 'R');
+ fprintf (file, "(%s)", reg_names[REGNO (addr.reg)]);
+ return;
- case ADDRESS_CONST_INT:
- case ADDRESS_SYMBOLIC:
- output_addr_const (file, x);
- return;
- }
+ case ADDRESS_CONST_INT:
+ case ADDRESS_SYMBOLIC:
+ output_addr_const (file, x);
+ return;
+ }
abort ();
}
\f
}
#endif
\f
+/* Return true if X is a small data address that can be rewritten
+ as a LO_SUM. */
+
+static bool
+mips_rewrite_small_data_p (rtx x)
+{
+ enum mips_symbol_type symbol_type;
+
+ return (TARGET_EXPLICIT_RELOCS
+ && mips_symbolic_constant_p (x, &symbol_type)
+ && symbol_type == SYMBOL_SMALL_DATA);
+}
+
+
+/* A for_each_rtx callback for small_data_pattern. */
+
+static int
+small_data_pattern_1 (rtx *loc, void *data ATTRIBUTE_UNUSED)
+{
+ if (GET_CODE (*loc) == LO_SUM)
+ return -1;
+
+ return mips_rewrite_small_data_p (*loc);
+}
+
+/* Return true if OP refers to small data symbols directly, not through
+ a LO_SUM. */
+
+int
+small_data_pattern (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
+{
+ return (GET_CODE (op) != SEQUENCE
+ && for_each_rtx (&op, small_data_pattern_1, 0));
+}
+\f
+/* A for_each_rtx callback, used by mips_rewrite_small_data. */
+
+static int
+mips_rewrite_small_data_1 (rtx *loc, void *data ATTRIBUTE_UNUSED)
+{
+ if (mips_rewrite_small_data_p (*loc))
+ *loc = gen_rtx_LO_SUM (Pmode, pic_offset_table_rtx, *loc);
+
+ if (GET_CODE (*loc) == LO_SUM)
+ return -1;
+
+ return 0;
+}
+
+/* If possible, rewrite OP so that it refers to small data using
+ explicit relocations. */
+
+rtx
+mips_rewrite_small_data (rtx op)
+{
+ op = copy_insn (op);
+ for_each_rtx (&op, mips_rewrite_small_data_1, 0);
+ return op;
+}
+\f
+/* Return true if the current function has an insn that implicitly
+ refers to $gp. */
+
+static bool
+mips_function_has_gp_insn (void)
+{
+ /* Don't bother rechecking if we found one last time. */
+ if (!cfun->machine->has_gp_insn_p)
+ {
+ rtx insn;
+
+ push_topmost_sequence ();
+ for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
+ if (INSN_P (insn)
+ && GET_CODE (PATTERN (insn)) != USE
+ && GET_CODE (PATTERN (insn)) != CLOBBER
+ && (get_attr_got (insn) != GOT_UNSET
+ || small_data_pattern (PATTERN (insn), VOIDmode)))
+ break;
+ pop_topmost_sequence ();
+
+ cfun->machine->has_gp_insn_p = (insn != 0);
+ }
+ return cfun->machine->has_gp_insn_p;
+}
+
+
/* Return the register that should be used as the global pointer
within this function. Return 0 if the function doesn't need
a global pointer. */
In cases like these, reload will have added the constant to the pool
but no instruction will yet refer to it. */
if (!regs_ever_live[GLOBAL_POINTER_REGNUM]
- && !current_function_uses_const_pool)
+ && !current_function_uses_const_pool
+ && !mips_function_has_gp_insn ())
return 0;
/* We need a global pointer, but perhaps we can use a call-clobbered
}
-/* Emit an instruction to move SRC into DEST. When generating
- explicit reloc code, mark the instruction as potentially dead. */
-
-static void
-mips_gp_insn (rtx dest, rtx src)
-{
- rtx insn;
-
- insn = emit_insn (gen_rtx_SET (VOIDmode, dest, src));
- if (TARGET_EXPLICIT_RELOCS)
- {
- /* compute_frame_size assumes that any function which uses the
- constant pool will need a gp. However, all constant
- pool references could be eliminated, in which case
- it is OK for flow to delete the gp load as well. */
- REG_NOTES (insn) = gen_rtx_EXPR_LIST (REG_MAYBE_DEAD, const0_rtx,
- REG_NOTES (insn));
- }
-}
-
/* Expand the prologue into a bunch of separate insns. */
void
/* If generating n32/n64 abicalls, emit the instructions to load $gp. */
if (TARGET_ABICALLS && TARGET_NEWABI && cfun->machine->global_pointer > 0)
{
- rtx fnsymbol, fnaddr;
-
- fnsymbol = XEXP (DECL_RTL (current_function_decl), 0);
- fnaddr = gen_rtx_REG (Pmode, PIC_FUNCTION_ADDR_REGNUM);
-
- mips_gp_insn (MIPS_PROLOGUE_TEMP (Pmode),
- mips_lui_reloc (fnsymbol, RELOC_LOADGP_HI));
- mips_gp_insn (MIPS_PROLOGUE_TEMP (Pmode),
- gen_rtx_PLUS (Pmode, MIPS_PROLOGUE_TEMP (Pmode), fnaddr));
- mips_gp_insn (pic_offset_table_rtx,
- gen_rtx_PLUS (Pmode, MIPS_PROLOGUE_TEMP (Pmode),
- mips_reloc (fnsymbol, RELOC_LOADGP_LO)));
+ rtx addr, offset;
+ addr = XEXP (DECL_RTL (current_function_decl), 0);
+ offset = mips_unspec_address (addr, SYMBOL_GOTOFF_LOADGP);
+ emit_insn (gen_loadgp (offset));
if (!TARGET_EXPLICIT_RELOCS)
emit_insn (gen_loadgp_blockage ());
}
hold the $gp value. */
static rtx
-mips_sdata_pointer (void)
+mips16_gp_pseudo_reg (void)
{
- if (TARGET_EXPLICIT_RELOCS)
- return pic_offset_table_rtx;
-
- if (!TARGET_MIPS16 || no_new_pseudos)
- return 0;
-
if (cfun->machine->mips16_gp_pseudo_rtx == NULL_RTX)
{
rtx const_gp;