summaryrefslogtreecommitdiff
path: root/gcc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc')
-rw-r--r--gcc/ChangeLog117
-rw-r--r--gcc/config/mips/mips-dsp.md9
-rw-r--r--gcc/config/mips/mips-protos.h10
-rw-r--r--gcc/config/mips/mips-ps-3d.md58
-rw-r--r--gcc/config/mips/mips.c859
-rw-r--r--gcc/config/mips/mips.h40
-rw-r--r--gcc/config/mips/mips.md301
-rw-r--r--gcc/config/mips/predicates.md8
-rw-r--r--gcc/testsuite/ChangeLog18
-rw-r--r--gcc/testsuite/gcc.target/mips/branch-10.c13
-rw-r--r--gcc/testsuite/gcc.target/mips/branch-11.c17
-rw-r--r--gcc/testsuite/gcc.target/mips/branch-12.c13
-rw-r--r--gcc/testsuite/gcc.target/mips/branch-13.c17
-rw-r--r--gcc/testsuite/gcc.target/mips/branch-14.c23
-rw-r--r--gcc/testsuite/gcc.target/mips/branch-15.c23
-rw-r--r--gcc/testsuite/gcc.target/mips/branch-2.c13
-rw-r--r--gcc/testsuite/gcc.target/mips/branch-3.c13
-rw-r--r--gcc/testsuite/gcc.target/mips/branch-4.c12
-rw-r--r--gcc/testsuite/gcc.target/mips/branch-5.c14
-rw-r--r--gcc/testsuite/gcc.target/mips/branch-6.c12
-rw-r--r--gcc/testsuite/gcc.target/mips/branch-7.c14
-rw-r--r--gcc/testsuite/gcc.target/mips/branch-8.c13
-rw-r--r--gcc/testsuite/gcc.target/mips/branch-9.c18
-rw-r--r--gcc/testsuite/gcc.target/mips/branch-helper.h37
24 files changed, 1287 insertions, 385 deletions
diff --git a/gcc/ChangeLog b/gcc/ChangeLog
index 8971af86ff4..75333c13f38 100644
--- a/gcc/ChangeLog
+++ b/gcc/ChangeLog
@@ -1,3 +1,120 @@
+2009-09-14 Richard Sandiford <rdsandiford@googlemail.com>
+
+ * config/mips/mips-protos.h (mips_cfun_has_cprestore_slot_p): Declare.
+ (mips_cprestore_address_p): Likewise.
+ (mips_save_gp_to_cprestore_slot): Likewise.
+ (mips_restore_gp): Rename to...
+ (mips_restore_gp_from_cprestore_slot): ...this.
+ (mips_must_initialize_gp_p): Declare.
+ (mips_emit_save_slot_move): Likewise.
+ (mips_output_load_label): Return nothing.
+ (mips_eh_uses): Declare.
+ * config/mips/mips.h (TARGET_SPLIT_CALLS): Require epilogue_completed.
+ (TARGET_CPRESTORE_DIRECTIVE): New macro.
+ (TARGET_ABSOLUTE_JUMPS): Likewise.
+ (EH_USES): Likewise.
+ (FIRST_PSEUDO_REGISTER): Update comment.
+ (MIPS_ABSOLUTE_JUMP): New macro, extracted from...
+ (MIPS_CALL): ...here.
+ (REGISTER_NAMES): Add $cprestore.
+ * config/mips/mips.c (machine_function): Remove has_gp_insn_p.
+ Add load_label_length, has_inflexible_gp_insn_p,
+ has_flexible_gp_insn_p, must_initialize_gp_p and
+ must_restore_gp_when_clobbered_p.
+ (mips_expand_call): Don't generate split instructions here.
+ (mips_split_call): Update the call to mips_restore_gp after
+ the above name change.
+ (mips16_cfun_returns_in_fpr_p): Move earlier in file.
+ (mips_find_gp_ref): New function.
+ (mips_insn_has_inflexible_gp_ref_p): Likewise.
+ (mips_cfun_has_inflexible_gp_ref_p): Likewise.
+ (mips_insn_has_flexible_gp_ref_p): Likewise.
+ (mips_cfun_has_flexible_gp_ref_p): Likewise.
+ (mips_function_has_gp_insn): Delete.
+ (mips_global_pointer): Drop the df_regs_ever_live_p check.
+ Use the new functions above. Only return INVALID_REGNUM
+ for TARGET_ABSOLUTE_JUMPS.
+ (mips_must_initialize_gp_p): New function.
+ (mips_get_cprestore_base_and_offset): New function, extracted from...
+ (mips_cprestore_slot): ...here. Take a bool parameter.
+ (mips_cfun_has_cprestore_slot_p): New function.
+ (mips_cprestore_address_p): Likewise.
+ (mips_save_gp_to_cprestore_slot): Likewise.
+ (mips_restore_gp): Rename to...
+ (mips_restore_gp_from_cprestore_slot): ...this. Assert
+ epilogue_completed. Update the call to mips_cprestore_slot.
+ Test cfun->machine->must_restore_gp_when_clobbered_p.
+ (mips_direct_save_slot_move_p): New function.
+ (mips_emit_save_slot_move): Likewise.
+ (mips_output_cplocal): Test mips_must_initialize_gp_p () instead
+ of cfun->machine->global_pointer.
+ (mips_output_function_prologue): Check mips_must_initialize_gp_p ().
+ (mips_save_reg): Use mips_emit_save_slot_move.
+ (mips_expand_prologue): Set must_initialize_gp_p.
+ Use mips_cfun_has_cprestore_slot_p. Use gen_potential_cprestore
+ for all cprestore saves. Emit a use_cprestore instruction after
+ setting up the cprestore slot.
+ (mips_restore_reg): Use mips_emit_save_slot_move.
+ (mips_process_load_label): New function.
+ (mips_load_label_length): Likewise.
+ (mips_output_load_label): Don't return asm: output it here instead.
+ Use mips_process_load_label.
+ (mips_adjust_insn_length): Adjust the length of branch instructions
+ that have length MAX_PIC_BRANCH_LENGTH.
+ (mips_output_conditional_branch): Update the call to
+ mips_output_load_label. Assume the branch target is OPERANDS[0]
+ rather than OPERANDS[1]. Use MIPS_ABSOLUTE_JUMP for absolute jumps.
+ (mips_output_order_conditional_branch): Swap the meaning of
+ OPERANDS[0] and OPERANDS[1].
+ (mips_variable_issue): Don't count ghost instructions.
+ (mips_expand_ghost_gp_insns): New function.
+ (mips_reorg): Rerun mips_reorg_process_insns if it returns true.
+ (mips_output_mi_thunk): Set must_initialize_gp_p.
+ (mips_eh_uses): New function.
+ * config/mips/predicates.md (cprestore_save_slot_operand)
+ (cprestore_load_slot_operand): New predicates.
+ * config/mips/mips.md (UNSPEC_POTENTIAL_CPRESTORE): New unspec.
+ (UNSPEC_MOVE_GP): Likewise.
+ (UNSPEC_CPRESTORE, UNSPEC_RESTORE_GP, UNSPEC_EH_RETURN)
+ (UNSPEC_CONSTTABLE_INT, UNSPEC_CONSTTABLE_FLOAT): Bump to make room.
+ (CPRESTORE_SLOT_REGNUM): New register.
+ (MAX_PIC_BRANCH_LENGTH): New constant.
+ (jal_macro): Use MIPS_ABSOLUTE_JUMPS.
+ (length): Use MAX_PIC_BRANCH_LENGTH as a placeholder for PIC long
+ branches. Fix commentary.
+ (loadgp_newabi_<mode>): Change from unspec_volatile to unspec.
+ Only split if mips_must_initialize_gp_p; expand to nothing otherwise.
+ Change type to "ghost".
+ (loadgp_absolute_<mode>): Likewise.
+ (loadgp_rtp_<mode>): Likewise.
+ (copygp_mips16): Likewise.
+ (loadgp_blockage): Remove redundant mode attribute.
+ (potential_cprestore): New instruction.
+ (cprestore): Turn into an unspec set.
+ (use_cprestore): New instruction.
+ (*branch_fp): Swap operands 0 and 1. Remove redundant mode attribute.
+ (*branch_fp_inverted): Likewise.
+ (*branch_order<mode>): Likewise.
+ (*branch_order<mode>_inverted): Likewise.
+ (*branch_equality<mode>): Likewise.
+ (*branch_equality<mode>_inverted): Likewise.
+ (*branch_bit<bbv><mode>): Likewise.
+ (*branch_bit<bbv><mode>_inverted): Likewise.
+ (*branch_equality<mode>_mips16): Remove redundant mode.
+ (jump): Turn into a define_expand.
+ (*jump_absolute): New instruction.
+ (*jump_pic): Likewise.
+ (*jump_mips16): Rename previously-unnamed pattern. Remove
+ redundant mode attribute.
+ (restore_gp): Split on epilogue_completed rather than
+ reload_completed. Change type to "ghost".
+ (move_gp<mode>): New instruction.
+ * config/mips/mips-dsp.md (mips_bposge): Swap operands 0 and 1.
+ Remove redundant mode attribute.
+ * config/mips/mips-ps-3d.md (bc1any4t): Likewise.
+ (bc1any4f, bc1any2t, bc1any2f): Likewise.
+ (*branch_upper_lower, *branch_upper_lower_inverted): Likewise.
+
2009-09-14 Michael Meissner <meissner@linux.vnet.ibm.com>
PR target/41210
diff --git a/gcc/config/mips/mips-dsp.md b/gcc/config/mips/mips-dsp.md
index 5112582d3a2..ff2004ccb54 100644
--- a/gcc/config/mips/mips-dsp.md
+++ b/gcc/config/mips/mips-dsp.md
@@ -1102,11 +1102,10 @@
(define_insn "mips_bposge"
[(set (pc)
(if_then_else (ge (reg:CCDSP CCDSP_PO_REGNUM)
- (match_operand:SI 0 "immediate_operand" "I"))
- (label_ref (match_operand 1 "" ""))
+ (match_operand:SI 1 "immediate_operand" "I"))
+ (label_ref (match_operand 0 "" ""))
(pc)))]
"ISA_HAS_DSP"
- "%*bposge%0\t%1%/"
- [(set_attr "type" "branch")
- (set_attr "mode" "none")])
+ "%*bposge%1\t%0%/"
+ [(set_attr "type" "branch")])
diff --git a/gcc/config/mips/mips-protos.h b/gcc/config/mips/mips-protos.h
index a1e28ce23c6..91fa72974e8 100644
--- a/gcc/config/mips/mips-protos.h
+++ b/gcc/config/mips/mips-protos.h
@@ -219,7 +219,10 @@ extern rtx mips_subword (rtx, bool);
extern bool mips_split_64bit_move_p (rtx, rtx);
extern void mips_split_doubleword_move (rtx, rtx);
extern const char *mips_output_move (rtx, rtx);
-extern void mips_restore_gp (rtx);
+extern bool mips_cfun_has_cprestore_slot_p (void);
+extern bool mips_cprestore_address_p (rtx, bool);
+extern void mips_save_gp_to_cprestore_slot (rtx, rtx, rtx, rtx);
+extern void mips_restore_gp_from_cprestore_slot (rtx);
#ifdef RTX_CODE
extern void mips_expand_scc (rtx *);
extern void mips_expand_conditional_branch (rtx *);
@@ -276,7 +279,9 @@ extern bool mips_small_data_pattern_p (rtx);
extern rtx mips_rewrite_small_data (rtx);
extern HOST_WIDE_INT mips_initial_elimination_offset (int, int);
extern rtx mips_return_addr (int, rtx);
+extern bool mips_must_initialize_gp_p (void);
extern enum mips_loadgp_style mips_current_loadgp_style (void);
+extern void mips_emit_save_slot_move (rtx, rtx, rtx);
extern void mips_expand_prologue (void);
extern void mips_expand_before_return (void);
extern void mips_expand_epilogue (bool);
@@ -296,7 +301,7 @@ extern int mips_register_move_cost (enum machine_mode, enum reg_class,
enum reg_class);
extern int mips_adjust_insn_length (rtx, int);
-extern const char *mips_output_load_label (void);
+extern void mips_output_load_label (rtx);
extern const char *mips_output_conditional_branch (rtx, rtx *, const char *,
const char *);
extern const char *mips_output_order_conditional_branch (rtx, rtx *, bool);
@@ -334,6 +339,7 @@ extern void mips_expand_atomic_qihi (union mips_gen_fn_ptrs,
extern void mips_expand_vector_init (rtx, rtx);
+extern bool mips_eh_uses (unsigned int);
extern bool mips_epilogue_uses (unsigned int);
extern void mips_final_prescan_insn (rtx, rtx *, int);
diff --git a/gcc/config/mips/mips-ps-3d.md b/gcc/config/mips/mips-ps-3d.md
index 98932d85b11..c13c7a69b28 100644
--- a/gcc/config/mips/mips-ps-3d.md
+++ b/gcc/config/mips/mips-ps-3d.md
@@ -439,50 +439,46 @@
; Branch on Any of Four Floating Point Condition Codes True
(define_insn "bc1any4t"
[(set (pc)
- (if_then_else (ne (match_operand:CCV4 0 "register_operand" "z")
+ (if_then_else (ne (match_operand:CCV4 1 "register_operand" "z")
(const_int 0))
- (label_ref (match_operand 1 "" ""))
+ (label_ref (match_operand 0 "" ""))
(pc)))]
"TARGET_HARD_FLOAT && TARGET_PAIRED_SINGLE_FLOAT"
- "%*bc1any4t\t%0,%1%/"
- [(set_attr "type" "branch")
- (set_attr "mode" "none")])
+ "%*bc1any4t\t%1,%0%/"
+ [(set_attr "type" "branch")])
; Branch on Any of Four Floating Point Condition Codes False
(define_insn "bc1any4f"
[(set (pc)
- (if_then_else (ne (match_operand:CCV4 0 "register_operand" "z")
+ (if_then_else (ne (match_operand:CCV4 1 "register_operand" "z")
(const_int -1))
- (label_ref (match_operand 1 "" ""))
+ (label_ref (match_operand 0 "" ""))
(pc)))]
"TARGET_HARD_FLOAT && TARGET_PAIRED_SINGLE_FLOAT"
- "%*bc1any4f\t%0,%1%/"
- [(set_attr "type" "branch")
- (set_attr "mode" "none")])
+ "%*bc1any4f\t%1,%0%/"
+ [(set_attr "type" "branch")])
; Branch on Any of Two Floating Point Condition Codes True
(define_insn "bc1any2t"
[(set (pc)
- (if_then_else (ne (match_operand:CCV2 0 "register_operand" "z")
+ (if_then_else (ne (match_operand:CCV2 1 "register_operand" "z")
(const_int 0))
- (label_ref (match_operand 1 "" ""))
+ (label_ref (match_operand 0 "" ""))
(pc)))]
"TARGET_HARD_FLOAT && TARGET_PAIRED_SINGLE_FLOAT"
- "%*bc1any2t\t%0,%1%/"
- [(set_attr "type" "branch")
- (set_attr "mode" "none")])
+ "%*bc1any2t\t%1,%0%/"
+ [(set_attr "type" "branch")])
; Branch on Any of Two Floating Point Condition Codes False
(define_insn "bc1any2f"
[(set (pc)
- (if_then_else (ne (match_operand:CCV2 0 "register_operand" "z")
+ (if_then_else (ne (match_operand:CCV2 1 "register_operand" "z")
(const_int -1))
- (label_ref (match_operand 1 "" ""))
+ (label_ref (match_operand 0 "" ""))
(pc)))]
"TARGET_HARD_FLOAT && TARGET_PAIRED_SINGLE_FLOAT"
- "%*bc1any2f\t%0,%1%/"
- [(set_attr "type" "branch")
- (set_attr "mode" "none")])
+ "%*bc1any2f\t%1,%0%/"
+ [(set_attr "type" "branch")])
; Used to access one register in a CCV2 pair. Operand 0 is the register
; pair and operand 1 is the index of the register we want (a CONST_INT).
@@ -497,45 +493,43 @@
(define_insn "*branch_upper_lower"
[(set (pc)
(if_then_else
- (match_operator 0 "equality_operator"
+ (match_operator 1 "equality_operator"
[(unspec:CC [(match_operand:CCV2 2 "register_operand" "z")
(match_operand 3 "const_int_operand")]
UNSPEC_SINGLE_CC)
(const_int 0)])
- (label_ref (match_operand 1 "" ""))
+ (label_ref (match_operand 0 "" ""))
(pc)))]
"TARGET_HARD_FLOAT"
{
operands[2]
= gen_rtx_REG (CCmode, REGNO (operands[2]) + INTVAL (operands[3]));
return mips_output_conditional_branch (insn, operands,
- MIPS_BRANCH ("b%F0", "%2,%1"),
- MIPS_BRANCH ("b%W0", "%2,%1"));
+ MIPS_BRANCH ("b%F1", "%2,%0"),
+ MIPS_BRANCH ("b%W1", "%2,%0"));
}
- [(set_attr "type" "branch")
- (set_attr "mode" "none")])
+ [(set_attr "type" "branch")])
; As above, but with the sense of the condition reversed.
(define_insn "*branch_upper_lower_inverted"
[(set (pc)
(if_then_else
- (match_operator 0 "equality_operator"
+ (match_operator 1 "equality_operator"
[(unspec:CC [(match_operand:CCV2 2 "register_operand" "z")
(match_operand 3 "const_int_operand")]
UNSPEC_SINGLE_CC)
(const_int 0)])
(pc)
- (label_ref (match_operand 1 "" ""))))]
+ (label_ref (match_operand 0 "" ""))))]
"TARGET_HARD_FLOAT"
{
operands[2]
= gen_rtx_REG (CCmode, REGNO (operands[2]) + INTVAL (operands[3]));
return mips_output_conditional_branch (insn, operands,
- MIPS_BRANCH ("b%W0", "%2,%1"),
- MIPS_BRANCH ("b%F0", "%2,%1"));
+ MIPS_BRANCH ("b%W1", "%2,%0"),
+ MIPS_BRANCH ("b%F1", "%2,%0"));
}
- [(set_attr "type" "branch")
- (set_attr "mode" "none")])
+ [(set_attr "type" "branch")])
;----------------------------------------------------------------------------
; Floating Point Reduced Precision Reciprocal Square Root Instructions.
diff --git a/gcc/config/mips/mips.c b/gcc/config/mips/mips.c
index 958abce0815..2b0df8f0adf 100644
--- a/gcc/config/mips/mips.c
+++ b/gcc/config/mips/mips.c
@@ -307,6 +307,10 @@ struct GTY(()) machine_function {
if the function doesn't need one. */
unsigned int global_pointer;
+ /* How many instructions it takes to load a label into $AT, or 0 if
+ this property hasn't yet been calculated. */
+ unsigned int load_label_length;
+
/* True if mips_adjust_insn_length should ignore an instruction's
hazard attribute. */
bool ignore_hazard_length_p;
@@ -315,8 +319,23 @@ struct GTY(()) machine_function {
.set nomacro. */
bool all_noreorder_p;
- /* True if the function is known to have an instruction that needs $gp. */
- bool has_gp_insn_p;
+ /* True if the function has "inflexible" and "flexible" references
+ to the global pointer. See mips_cfun_has_inflexible_gp_ref_p
+ and mips_cfun_has_flexible_gp_ref_p for details. */
+ bool has_inflexible_gp_insn_p;
+ bool has_flexible_gp_insn_p;
+
+ /* True if the function's prologue must load the global pointer
+ value into pic_offset_table_rtx and store the same value in
+ the function's cprestore slot (if any). Even if this value
+ is currently false, we may decide to set it to true later;
+ see mips_must_initialize_gp_p () for details. */
+ bool must_initialize_gp_p;
+
+ /* True if the current function must restore $gp after any potential
+ clobber. This value is only meaningful during the first post-epilogue
+ split_insns pass; see mips_must_initialize_gp_p () for details. */
+ bool must_restore_gp_when_clobbered_p;
/* True if we have emitted an instruction to initialize
mips16_gp_pseudo_rtx. */
@@ -6313,9 +6332,7 @@ mips_expand_call (enum mips_call_type type, rtx result, rtx addr,
{
rtx (*fn) (rtx, rtx);
- if (type == MIPS_CALL_EPILOGUE && TARGET_SPLIT_CALLS)
- fn = gen_call_split;
- else if (type == MIPS_CALL_SIBCALL)
+ if (type == MIPS_CALL_SIBCALL)
fn = gen_sibcall_internal;
else
fn = gen_call_internal;
@@ -6328,9 +6345,7 @@ mips_expand_call (enum mips_call_type type, rtx result, rtx addr,
rtx (*fn) (rtx, rtx, rtx, rtx);
rtx reg1, reg2;
- if (type == MIPS_CALL_EPILOGUE && TARGET_SPLIT_CALLS)
- fn = gen_call_value_multiple_split;
- else if (type == MIPS_CALL_SIBCALL)
+ if (type == MIPS_CALL_SIBCALL)
fn = gen_sibcall_value_multiple_internal;
else
fn = gen_call_value_multiple_internal;
@@ -6343,9 +6358,7 @@ mips_expand_call (enum mips_call_type type, rtx result, rtx addr,
{
rtx (*fn) (rtx, rtx, rtx);
- if (type == MIPS_CALL_EPILOGUE && TARGET_SPLIT_CALLS)
- fn = gen_call_value_split;
- else if (type == MIPS_CALL_SIBCALL)
+ if (type == MIPS_CALL_SIBCALL)
fn = gen_sibcall_value_internal;
else
fn = gen_call_value_internal;
@@ -6375,7 +6388,7 @@ mips_split_call (rtx insn, rtx call_pattern)
/* Pick a temporary register that is suitable for both MIPS16 and
non-MIPS16 code. $4 and $5 are used for returning complex double
values in soft-float code, so $6 is the first suitable candidate. */
- mips_restore_gp (gen_rtx_REG (Pmode, GP_ARG_FIRST + 2));
+ mips_restore_gp_from_cprestore_slot (gen_rtx_REG (Pmode, GP_ARG_FIRST + 2));
}
/* Implement TARGET_FUNCTION_OK_FOR_SIBCALL. */
@@ -8566,42 +8579,131 @@ mips16e_output_save_restore (rtx pattern, HOST_WIDE_INT adjust)
return buffer;
}
-/* Return true if the current function has an insn that implicitly
- refers to $gp. */
+/* Return true if the current function returns its value in a floating-point
+ register in MIPS16 mode. */
static bool
-mips_function_has_gp_insn (void)
+mips16_cfun_returns_in_fpr_p (void)
{
- /* Don't bother rechecking if we found one last time. */
- if (!cfun->machine->has_gp_insn_p)
- {
- rtx insn;
+ tree return_type = DECL_RESULT (current_function_decl);
+ return (TARGET_MIPS16
+ && TARGET_HARD_FLOAT_ABI
+ && !aggregate_value_p (return_type, current_function_decl)
+ && mips_return_mode_in_fpr_p (DECL_MODE (return_type)));
+}
+/* Return true if predicate PRED is true for at least one instruction.
+ Cache the result in *CACHE, and assume that the result is true
+ if *CACHE is already true. */
+
+static bool
+mips_find_gp_ref (bool *cache, bool (*pred) (rtx))
+{
+ rtx insn;
+
+ if (!*cache)
+ {
push_topmost_sequence ();
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
- if (USEFUL_INSN_P (insn)
- && (get_attr_got (insn) != GOT_UNSET
- || mips_small_data_pattern_p (PATTERN (insn))))
+ if (USEFUL_INSN_P (insn) && pred (insn))
{
- cfun->machine->has_gp_insn_p = true;
+ *cache = true;
break;
}
pop_topmost_sequence ();
}
- return cfun->machine->has_gp_insn_p;
+ return *cache;
}
-/* Return true if the current function returns its value in a floating-point
- register in MIPS16 mode. */
+/* Return true if INSN refers to the global pointer in an "inflexible" way.
+ See mips_cfun_has_inflexible_gp_ref_p for details. */
static bool
-mips16_cfun_returns_in_fpr_p (void)
+mips_insn_has_inflexible_gp_ref_p (rtx insn)
{
- tree return_type = DECL_RESULT (current_function_decl);
- return (TARGET_MIPS16
- && TARGET_HARD_FLOAT_ABI
- && !aggregate_value_p (return_type, current_function_decl)
- && mips_return_mode_in_fpr_p (DECL_MODE (return_type)));
+ /* Uses of pic_offset_table_rtx in CALL_INSN_FUNCTION_USAGE
+ indicate that the target could be a traditional MIPS
+ lazily-binding stub. */
+ return find_reg_fusage (insn, USE, pic_offset_table_rtx);
+}
+
+/* Return true if the current function refers to the global pointer
+ in a way that forces $28 to be valid. This means that we can't
+ change the choice of global pointer, even for NewABI code.
+
+ One example of this (and one which needs several checks) is that
+ $28 must be valid when calling traditional MIPS lazy-binding stubs.
+ (This restriction does not apply to PLTs.) */
+
+static bool
+mips_cfun_has_inflexible_gp_ref_p (void)
+{
+ /* If the function has a nonlocal goto, $28 must hold the correct
+ global pointer for the target function. That is, the target
+ of the goto implicitly uses $28. */
+ if (crtl->has_nonlocal_goto)
+ return true;
+
+ if (TARGET_ABICALLS_PIC2)
+ {
+ /* Symbolic accesses implicitly use the global pointer unless
+ -mexplicit-relocs is in effect. JAL macros to symbolic addresses
+ might go to traditional MIPS lazy-binding stubs. */
+ if (!TARGET_EXPLICIT_RELOCS)
+ return true;
+
+ /* FUNCTION_PROFILER includes a JAL to _mcount, which again
+ can be lazily-bound. */
+ if (crtl->profile)
+ return true;
+
+ /* MIPS16 functions that return in FPRs need to call an
+ external libgcc routine. This call is only made explict
+ during mips_expand_epilogue, and it too might be lazily bound. */
+ if (mips16_cfun_returns_in_fpr_p ())
+ return true;
+ }
+
+ return mips_find_gp_ref (&cfun->machine->has_inflexible_gp_insn_p,
+ mips_insn_has_inflexible_gp_ref_p);
+}
+
+/* Return true if INSN refers to the global pointer in a "flexible" way.
+ See mips_cfun_has_flexible_gp_ref_p for details. */
+
+static bool
+mips_insn_has_flexible_gp_ref_p (rtx insn)
+{
+ return (get_attr_got (insn) != GOT_UNSET
+ || mips_small_data_pattern_p (PATTERN (insn))
+ || reg_overlap_mentioned_p (pic_offset_table_rtx, PATTERN (insn)));
+}
+
+/* Return true if the current function references the global pointer,
+ but if those references do not inherently require the global pointer
+ to be $28. Assume !mips_cfun_has_inflexible_gp_ref_p (). */
+
+static bool
+mips_cfun_has_flexible_gp_ref_p (void)
+{
+ /* Reload can sometimes introduce constant pool references
+ into a function that otherwise didn't need them. For example,
+ suppose we have an instruction like:
+
+ (set (reg:DF R1) (float:DF (reg:SI R2)))
+
+ If R2 turns out to be a constant such as 1, the instruction may
+ have a REG_EQUAL note saying that R1 == 1.0. Reload then has
+ the option of using this constant if R2 doesn't get allocated
+ to a register.
+
+ In cases like these, reload will have added the constant to the
+ pool but no instruction will yet refer to it. */
+ if (TARGET_ABICALLS_PIC2 && !reload_completed && crtl->uses_const_pool)
+ return true;
+
+ return mips_find_gp_ref (&cfun->machine->has_flexible_gp_insn_p,
+ mips_insn_has_flexible_gp_ref_p);
}
/* Return the register that should be used as the global pointer
@@ -8617,57 +8719,18 @@ mips_global_pointer (void)
if (!TARGET_USE_GOT)
return GLOBAL_POINTER_REGNUM;
- /* We must always provide $gp when it is used implicitly. */
- if (!TARGET_EXPLICIT_RELOCS)
- return GLOBAL_POINTER_REGNUM;
-
- /* FUNCTION_PROFILER includes a jal macro, so we need to give it
- a valid gp. */
- if (crtl->profile)
- return GLOBAL_POINTER_REGNUM;
-
- /* If the function has a nonlocal goto, $gp must hold the correct
- global pointer for the target function. */
- if (crtl->has_nonlocal_goto)
+ /* If there are inflexible references to $gp, we must use the
+ standard register. */
+ if (mips_cfun_has_inflexible_gp_ref_p ())
return GLOBAL_POINTER_REGNUM;
- /* There's no need to initialize $gp if it isn't referenced now,
- and if we can be sure that no new references will be added during
- or after reload. */
- if (!df_regs_ever_live_p (GLOBAL_POINTER_REGNUM)
- && !mips_function_has_gp_insn ())
- {
- /* The function doesn't use $gp at the moment. If we're generating
- -call_nonpic code, no new uses will be introduced during or after
- reload. */
- if (TARGET_ABICALLS_PIC0)
- return INVALID_REGNUM;
-
- /* We need to handle the following implicit gp references:
-
- - Reload can sometimes introduce constant pool references
- into a function that otherwise didn't need them. For example,
- suppose we have an instruction like:
-
- (set (reg:DF R1) (float:DF (reg:SI R2)))
-
- If R2 turns out to be constant such as 1, the instruction may
- have a REG_EQUAL note saying that R1 == 1.0. Reload then has
- the option of using this constant if R2 doesn't get allocated
- to a register.
-
- In cases like these, reload will have added the constant to the
- pool but no instruction will yet refer to it.
+ /* If there are no current references to $gp, then the only uses
+ we can introduce later are those involved in long branches. */
+ if (TARGET_ABSOLUTE_JUMPS && !mips_cfun_has_flexible_gp_ref_p ())
+ return INVALID_REGNUM;
- - MIPS16 functions that return in FPRs need to call an
- external libgcc routine. */
- if (!crtl->uses_const_pool
- && !mips16_cfun_returns_in_fpr_p ())
- return INVALID_REGNUM;
- }
-
- /* We need a global pointer, but perhaps we can use a call-clobbered
- register instead of $gp. */
+ /* If the global pointer is call-saved, try to use a call-clobbered
+ alternative. */
if (TARGET_CALL_SAVED_GP && current_function_is_leaf)
for (regno = GP_REG_FIRST; regno <= GP_REG_LAST; regno++)
if (!df_regs_ever_live_p (regno)
@@ -8679,6 +8742,119 @@ mips_global_pointer (void)
return GLOBAL_POINTER_REGNUM;
}
+/* Return true if current function's prologue must load the global
+ pointer value into pic_offset_table_rtx and store the same value in
+ the function's cprestore slot (if any).
+
+ One problem we have to deal with is that, when emitting GOT-based
+ position independent code, long-branch sequences will need to load
+ the address of the branch target from the GOT. We don't know until
+ the very end of compilation whether (and where) the function needs
+ long branches, so we must ensure that _any_ branch can access the
+ global pointer in some form. However, we do not want to pessimize
+ the usual case in which all branches are short.
+
+ We handle this as follows:
+
+ (1) During reload, we set cfun->machine->global_pointer to
+ INVALID_REGNUM if we _know_ that the current function
+ doesn't need a global pointer. This is only valid if
+ long branches don't need the GOT.
+
+ Otherwise, we assume that we might need a global pointer
+ and pick an appropriate register.
+
+ (2) If cfun->machine->global_pointer != INVALID_REGNUM,
+ we ensure that the global pointer is available at every
+ block boundary bar entry and exit. We do this in one of two ways:
+
+ - If the function has a cprestore slot, we ensure that this
+ slot is valid at every branch. However, as explained in
+ point (6) below, there is no guarantee that pic_offset_table_rtx
+ itself is valid if new uses of the global pointer are introduced
+ after the first post-epilogue split.
+
+ We guarantee that the cprestore slot is valid by loading it
+ into a fake register, CPRESTORE_SLOT_REGNUM. We then make
+ this register live at every block boundary bar function entry
+ and exit. It is then invalid to move the load (and thus the
+ preceding store) across a block boundary.
+
+ - If the function has no cprestore slot, we guarantee that
+ pic_offset_table_rtx itself is valid at every branch.
+
+ See mips_eh_uses for the handling of the register liveness.
+
+ (3) During prologue and epilogue generation, we emit "ghost"
+ placeholder instructions to manipulate the global pointer.
+
+ (4) During prologue generation, we set cfun->machine->must_initialize_gp_p
+ and cfun->machine->must_restore_gp_when_clobbered_p if we already know
+ that the function needs a global pointer. (There is no need to set
+ them earlier than this, and doing it as late as possible leads to
+ fewer false positives.)
+
+ (5) If cfun->machine->must_initialize_gp_p is true during a
+ split_insns pass, we split the ghost instructions into real
+ instructions. These split instructions can then be optimized in
+ the usual way. Otherwise, we keep the ghost instructions intact,
+ and optimize for the case where they aren't needed. We still
+ have the option of splitting them later, if we need to introduce
+ new uses of the global pointer.
+
+ For example, the scheduler ignores a ghost instruction that
+ stores $28 to the stack, but it handles the split form of
+ the ghost instruction as an ordinary store.
+
+ (6) [OldABI only.] If cfun->machine->must_restore_gp_when_clobbered_p
+ is true during the first post-epilogue split_insns pass, we split
+ calls and restore_gp patterns into instructions that explicitly
+ load pic_offset_table_rtx from the cprestore slot. Otherwise,
+ we split these patterns into instructions that _don't_ load from
+ the cprestore slot.
+
+ If cfun->machine->must_restore_gp_when_clobbered_p is true at the
+ time of the split, then any instructions that exist at that time
+ can make free use of pic_offset_table_rtx. However, if we want
+ to introduce new uses of the global pointer after the split,
+ we must explicitly load the value from the cprestore slot, since
+ pic_offset_table_rtx itself might not be valid at a given point
+ in the function.
+
+ The idea is that we want to be able to delete redundant
+ loads from the cprestore slot in the usual case where no
+ long branches are needed.
+
+ (7) If cfun->machine->must_initialize_gp_p is still false at the end
+ of md_reorg, we decide whether the global pointer is needed for
+ long branches. If so, we set cfun->machine->must_initialize_gp_p
+ to true and split the ghost instructions into real instructions
+ at that stage.
+
+ Note that the ghost instructions must have a zero length for three reasons:
+
+ - Giving the length of the underlying $gp sequence might cause
+ us to use long branches in cases where they aren't really needed.
+
+ - They would perturb things like alignment calculations.
+
+ - More importantly, the hazard detection in md_reorg relies on
+ empty instructions having a zero length.
+
+ If we find a long branch and split the ghost instructions at the
+ end of md_reorg, the split could introduce more long branches.
+ That isn't a problem though, because we still do the split before
+ the final shorten_branches pass.
+
+ This is extremely ugly, but it seems like the best compromise between
+ correctness and efficiency. */
+
+bool
+mips_must_initialize_gp_p (void)
+{
+ return cfun->machine->must_initialize_gp_p;
+}
+
/* Return true if REGNO is a register that is ordinarily call-clobbered
but must nevertheless be preserved by an interrupt handler. */
@@ -9198,48 +9374,118 @@ mips_set_return_address (rtx address, rtx scratch)
mips_emit_move (gen_frame_mem (GET_MODE (address), slot_address), address);
}
-/* Return a MEM rtx for the cprestore slot, using TEMP as a temporary base
- register if need be. */
+/* Return true if the current function has a cprestore slot. */
-static rtx
-mips_cprestore_slot (rtx temp)
+bool
+mips_cfun_has_cprestore_slot_p (void)
+{
+ return (cfun->machine->global_pointer != INVALID_REGNUM
+ && cfun->machine->frame.cprestore_size > 0);
+}
+
+/* Fill *BASE and *OFFSET such that *BASE + *OFFSET refers to the
+ cprestore slot. LOAD_P is true if the caller wants to load from
+ the cprestore slot; it is false if the caller wants to store to
+ the slot. */
+
+static void
+mips_get_cprestore_base_and_offset (rtx *base, HOST_WIDE_INT *offset,
+ bool load_p)
{
const struct mips_frame_info *frame;
- rtx base;
- HOST_WIDE_INT offset;
frame = &cfun->machine->frame;
- if (frame_pointer_needed)
- {
- base = hard_frame_pointer_rtx;
- offset = frame->args_size - frame->hard_frame_pointer_offset;
+ /* .cprestore always uses the stack pointer instead of the frame pointer.
+ We have a free choice for direct stores for non-MIPS16 functions,
+ and for MIPS16 functions whose cprestore slot is in range of the
+ stack pointer. Using the stack pointer would sometimes give more
+ (early) scheduling freedom, but using the frame pointer would
+ sometimes give more (late) scheduling freedom. It's hard to
+ predict which applies to a given function, so let's keep things
+ simple.
+
+ Loads must always use the frame pointer in functions that call
+ alloca, and there's little benefit to using the stack pointer
+ otherwise. */
+ if (frame_pointer_needed && !(TARGET_CPRESTORE_DIRECTIVE && !load_p))
+ {
+ *base = hard_frame_pointer_rtx;
+ *offset = frame->args_size - frame->hard_frame_pointer_offset;
}
else
{
- base = stack_pointer_rtx;
- offset = frame->args_size;
+ *base = stack_pointer_rtx;
+ *offset = frame->args_size;
}
+}
+
+/* Return true if X is the load or store address of the cprestore slot;
+ LOAD_P says which. */
+
+bool
+mips_cprestore_address_p (rtx x, bool load_p)
+{
+ rtx given_base, required_base;
+ HOST_WIDE_INT given_offset, required_offset;
+
+ mips_split_plus (x, &given_base, &given_offset);
+ mips_get_cprestore_base_and_offset (&required_base, &required_offset, load_p);
+ return given_base == required_base && given_offset == required_offset;
+}
+
+/* Return a MEM rtx for the cprestore slot. LOAD_P is true if we are
+ going to load from it, false if we are going to store to it.
+ Use TEMP as a temporary register if need be. */
+
+static rtx
+mips_cprestore_slot (rtx temp, bool load_p)
+{
+ rtx base;
+ HOST_WIDE_INT offset;
+
+ mips_get_cprestore_base_and_offset (&base, &offset, load_p);
return gen_frame_mem (Pmode, mips_add_offset (temp, base, offset));
}
+/* Emit instructions to save global pointer value GP into cprestore
+ slot MEM. OFFSET is the offset that MEM applies to the base register.
+
+ MEM may not be a legitimate address. If it isn't, TEMP is a
+ temporary register that can be used, otherwise it is a SCRATCH. */
+
+void
+mips_save_gp_to_cprestore_slot (rtx mem, rtx offset, rtx gp, rtx temp)
+{
+ if (TARGET_CPRESTORE_DIRECTIVE)
+ {
+ gcc_assert (gp == pic_offset_table_rtx);
+ emit_insn (gen_cprestore (mem, offset));
+ }
+ else
+ mips_emit_move (mips_cprestore_slot (temp, false), gp);
+}
+
/* Restore $gp from its save slot, using TEMP as a temporary base register
- if need be. This function is for o32 and o64 abicalls only. */
+ if need be. This function is for o32 and o64 abicalls only.
+
+ See mips_must_initialize_gp_p for details about how we manage the
+ global pointer. */
void
-mips_restore_gp (rtx temp)
+mips_restore_gp_from_cprestore_slot (rtx temp)
{
- gcc_assert (TARGET_ABICALLS && TARGET_OLDABI);
+ gcc_assert (TARGET_ABICALLS && TARGET_OLDABI && epilogue_completed);
- if (cfun->machine->global_pointer == INVALID_REGNUM)
+ if (!cfun->machine->must_restore_gp_when_clobbered_p)
return;
if (TARGET_MIPS16)
{
- mips_emit_move (temp, mips_cprestore_slot (temp));
+ mips_emit_move (temp, mips_cprestore_slot (temp, true));
mips_emit_move (pic_offset_table_rtx, temp);
}
else
- mips_emit_move (pic_offset_table_rtx, mips_cprestore_slot (temp));
+ mips_emit_move (pic_offset_table_rtx, mips_cprestore_slot (temp, true));
if (!TARGET_EXPLICIT_RELOCS)
emit_insn (gen_blockage ());
}
@@ -9327,6 +9573,89 @@ mips_for_each_saved_gpr_and_fpr (HOST_WIDE_INT sp_offset,
offset -= GET_MODE_SIZE (fpr_mode);
}
}
+
+/* Return true if a move between register REGNO and its save slot (MEM)
+ can be done in a single move. LOAD_P is true if we are loading
+ from the slot, false if we are storing to it. */
+
+static bool
+mips_direct_save_slot_move_p (unsigned int regno, rtx mem, bool load_p)
+{
+ /* There is a specific MIPS16 instruction for saving $31 to the stack. */
+ if (TARGET_MIPS16 && !load_p && regno == GP_REG_FIRST + 31)
+ return false;
+
+ return mips_secondary_reload_class (REGNO_REG_CLASS (regno),
+ GET_MODE (mem), mem, load_p) == NO_REGS;
+}
+
+/* Emit a move from SRC to DEST, given that one of them is a register
+ save slot and that the other is a register. TEMP is a temporary
+ GPR of the same mode that is available if need be. */
+
+void
+mips_emit_save_slot_move (rtx dest, rtx src, rtx temp)
+{
+ unsigned int regno;
+ rtx mem;
+
+ if (REG_P (src))
+ {
+ regno = REGNO (src);
+ mem = dest;
+ }
+ else
+ {
+ regno = REGNO (dest);
+ mem = src;
+ }
+
+ if (regno == cfun->machine->global_pointer && !mips_must_initialize_gp_p ())
+ {
+ /* We don't yet know whether we'll need this instruction or not.
+ Postpone the decision by emitting a ghost move. This move
+ is specifically not frame-related; only the split version is. */
+ if (TARGET_64BIT)
+ emit_insn (gen_move_gpdi (dest, src));
+ else
+ emit_insn (gen_move_gpsi (dest, src));
+ return;
+ }
+
+ if (regno == HI_REGNUM)
+ {
+ if (REG_P (dest))
+ {
+ mips_emit_move (temp, src);
+ if (TARGET_64BIT)
+ emit_insn (gen_mthisi_di (gen_rtx_REG (TImode, MD_REG_FIRST),
+ temp, gen_rtx_REG (DImode, LO_REGNUM)));
+ else
+ emit_insn (gen_mthisi_di (gen_rtx_REG (DImode, MD_REG_FIRST),
+ temp, gen_rtx_REG (SImode, LO_REGNUM)));
+ }
+ else
+ {
+ if (TARGET_64BIT)
+ emit_insn (gen_mfhidi_ti (temp,
+ gen_rtx_REG (TImode, MD_REG_FIRST)));
+ else
+ emit_insn (gen_mfhisi_di (temp,
+ gen_rtx_REG (DImode, MD_REG_FIRST)));
+ mips_emit_move (dest, temp);
+ }
+ }
+ else if (mips_direct_save_slot_move_p (regno, mem, mem == src))
+ mips_emit_move (dest, src);
+ else
+ {
+ gcc_assert (!reg_overlap_mentioned_p (dest, temp));
+ mips_emit_move (temp, src);
+ mips_emit_move (dest, temp);
+ }
+ if (MEM_P (dest))
+ mips_set_frame_expr (mips_frame_set (dest, src));
+}
/* If we're generating n32 or n64 abicalls, and the current function
does not use $28 as its global pointer, emit a cplocal directive.
@@ -9336,7 +9665,7 @@ static void
mips_output_cplocal (void)
{
if (!TARGET_EXPLICIT_RELOCS
- && cfun->machine->global_pointer != INVALID_REGNUM
+ && mips_must_initialize_gp_p ()
&& cfun->machine->global_pointer != GLOBAL_POINTER_REGNUM)
output_asm_insn (".cplocal %+", 0);
}
@@ -9408,7 +9737,8 @@ mips_output_function_prologue (FILE *file, HOST_WIDE_INT size ATTRIBUTE_UNUSED)
/* Handle the initialization of $gp for SVR4 PIC, if applicable.
Also emit the ".set noreorder; .set nomacro" sequence for functions
that need it. */
- if (mips_current_loadgp_style () == LOADGP_OLDABI)
+ if (mips_must_initialize_gp_p ()
+ && mips_current_loadgp_style () == LOADGP_OLDABI)
{
if (TARGET_MIPS16)
{
@@ -9490,33 +9820,7 @@ mips_save_reg (rtx reg, rtx mem)
mips_set_frame_expr (gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, x1, x2)));
}
else
- {
- if (REGNO (reg) == HI_REGNUM)
- {
- if (TARGET_64BIT)
- emit_insn (gen_mfhidi_ti (MIPS_PROLOGUE_TEMP (DImode),
- gen_rtx_REG (TImode, MD_REG_FIRST)));
- else
- emit_insn (gen_mfhisi_di (MIPS_PROLOGUE_TEMP (SImode),
- gen_rtx_REG (DImode, MD_REG_FIRST)));
- mips_emit_move (mem, MIPS_PROLOGUE_TEMP (GET_MODE (reg)));
- }
- else if ((TARGET_MIPS16
- && REGNO (reg) != GP_REG_FIRST + 31
- && !M16_REG_P (REGNO (reg)))
- || ACC_REG_P (REGNO (reg)))
- {
- /* If the register has no direct store instruction, move it
- through a temporary. Note that there's a special MIPS16
- instruction to save $31. */
- mips_emit_move (MIPS_PROLOGUE_TEMP (GET_MODE (reg)), reg);
- mips_emit_move (mem, MIPS_PROLOGUE_TEMP (GET_MODE (reg)));
- }
- else
- mips_emit_move (mem, reg);
-
- mips_set_frame_expr (mips_frame_set (mem, reg));
- }
+ mips_emit_save_slot_move (mem, reg, MIPS_PROLOGUE_TEMP (GET_MODE (reg)));
}
/* The __gnu_local_gp symbol. */
@@ -9599,7 +9903,19 @@ mips_expand_prologue (void)
rtx insn;
if (cfun->machine->global_pointer != INVALID_REGNUM)
- SET_REGNO (pic_offset_table_rtx, cfun->machine->global_pointer);
+ {
+ /* Check whether an insn uses pic_offset_table_rtx, either explicitly
+ or implicitly. If so, we can commit to using a global pointer
+ straight away, otherwise we need to defer the decision. */
+ if (mips_cfun_has_inflexible_gp_ref_p ()
+ || mips_cfun_has_flexible_gp_ref_p ())
+ {
+ cfun->machine->must_initialize_gp_p = true;
+ cfun->machine->must_restore_gp_when_clobbered_p = true;
+ }
+
+ SET_REGNO (pic_offset_table_rtx, cfun->machine->global_pointer);
+ }
frame = &cfun->machine->frame;
size = frame->total_size;
@@ -9801,17 +10117,22 @@ mips_expand_prologue (void)
mips_emit_loadgp ();
/* Initialize the $gp save slot. */
- if (frame->cprestore_size > 0
- && cfun->machine->global_pointer != INVALID_REGNUM)
+ if (mips_cfun_has_cprestore_slot_p ())
{
- if (TARGET_MIPS16)
- mips_emit_move (mips_cprestore_slot (MIPS_PROLOGUE_TEMP (Pmode)),
- MIPS16_PIC_TEMP);
- else if (TARGET_ABICALLS_PIC2)
- emit_insn (gen_cprestore (GEN_INT (frame->args_size)));
- else
- emit_move_insn (mips_cprestore_slot (MIPS_PROLOGUE_TEMP (Pmode)),
- pic_offset_table_rtx);
+ rtx base, mem, gp, temp;
+ HOST_WIDE_INT offset;
+
+ mips_get_cprestore_base_and_offset (&base, &offset, false);
+ mem = gen_frame_mem (Pmode, plus_constant (base, offset));
+ gp = TARGET_MIPS16 ? MIPS16_PIC_TEMP : pic_offset_table_rtx;
+ temp = (SMALL_OPERAND (offset)
+ ? gen_rtx_SCRATCH (Pmode)
+ : MIPS_PROLOGUE_TEMP (Pmode));
+ emit_insn (gen_potential_cprestore (mem, GEN_INT (offset), gp, temp));
+
+ mips_get_cprestore_base_and_offset (&base, &offset, true);
+ mem = gen_frame_mem (Pmode, plus_constant (base, offset));
+ emit_insn (gen_use_cprestore (mem));
}
/* We need to search back to the last use of K0 or K1. */
@@ -9844,27 +10165,7 @@ mips_restore_reg (rtx reg, rtx mem)
if (TARGET_MIPS16 && REGNO (reg) == GP_REG_FIRST + 31)
reg = gen_rtx_REG (GET_MODE (reg), GP_REG_FIRST + 7);
- if (REGNO (reg) == HI_REGNUM)
- {
- mips_emit_move (MIPS_EPILOGUE_TEMP (GET_MODE (reg)), mem);
- if (TARGET_64BIT)
- emit_insn (gen_mthisi_di (gen_rtx_REG (TImode, MD_REG_FIRST),
- MIPS_EPILOGUE_TEMP (DImode),
- gen_rtx_REG (DImode, LO_REGNUM)));
- else
- emit_insn (gen_mthisi_di (gen_rtx_REG (DImode, MD_REG_FIRST),
- MIPS_EPILOGUE_TEMP (SImode),
- gen_rtx_REG (SImode, LO_REGNUM)));
- }
- else if ((TARGET_MIPS16 && !M16_REG_P (REGNO (reg)))
- || ACC_REG_P (REGNO (reg)))
- {
- /* Can't restore directly; move through a temporary. */
- mips_emit_move (MIPS_EPILOGUE_TEMP (GET_MODE (reg)), mem);
- mips_emit_move (reg, MIPS_EPILOGUE_TEMP (GET_MODE (reg)));
- }
- else
- mips_emit_move (reg, mem);
+ mips_emit_save_slot_move (reg, mem, MIPS_EPILOGUE_TEMP (GET_MODE (reg)));
}
/* Emit any instructions needed before a return. */
@@ -10732,12 +11033,112 @@ mips_init_libfuncs (void)
synchronize_libfunc = init_one_libfunc ("__sync_synchronize");
}
+/* Build up a multi-insn sequence that loads label TARGET into $AT. */
+
+static void
+mips_process_load_label (rtx target)
+{
+ rtx base, gp, intop;
+ HOST_WIDE_INT offset;
+
+ mips_multi_start ();
+ switch (mips_abi)
+ {
+ case ABI_N32:
+ mips_multi_add_insn ("lw\t%@,%%got_page(%0)(%+)", target, 0);
+ mips_multi_add_insn ("addiu\t%@,%@,%%got_ofst(%0)", target, 0);
+ break;
+
+ case ABI_64:
+ mips_multi_add_insn ("ld\t%@,%%got_page(%0)(%+)", target, 0);
+ mips_multi_add_insn ("daddiu\t%@,%@,%%got_ofst(%0)", target, 0);
+ break;
+
+ default:
+ gp = pic_offset_table_rtx;
+ if (mips_cfun_has_cprestore_slot_p ())
+ {
+ gp = gen_rtx_REG (Pmode, AT_REGNUM);
+ mips_get_cprestore_base_and_offset (&base, &offset, true);
+ if (!SMALL_OPERAND (offset))
+ {
+ intop = GEN_INT (CONST_HIGH_PART (offset));
+ mips_multi_add_insn ("lui\t%0,%1", gp, intop, 0);
+ mips_multi_add_insn ("addu\t%0,%0,%1", gp, base, 0);
+
+ base = gp;
+ offset = CONST_LOW_PART (offset);
+ }
+ intop = GEN_INT (offset);
+ if (ISA_HAS_LOAD_DELAY)
+ mips_multi_add_insn ("lw\t%0,%1(%2)%#", gp, intop, base, 0);
+ else
+ mips_multi_add_insn ("lw\t%0,%1(%2)", gp, intop, base, 0);
+ }
+ if (ISA_HAS_LOAD_DELAY)
+ mips_multi_add_insn ("lw\t%@,%%got(%0)(%1)%#", target, gp, 0);
+ else
+ mips_multi_add_insn ("lw\t%@,%%got(%0)(%1)", target, gp, 0);
+ mips_multi_add_insn ("addiu\t%@,%@,%%lo(%0)", target, 0);
+ break;
+ }
+}
+
+/* Return the number of instructions needed to load a label into $AT. */
+
+static unsigned int
+mips_load_label_length (void)
+{
+ if (cfun->machine->load_label_length == 0)
+ {
+ mips_process_load_label (pc_rtx);
+ cfun->machine->load_label_length = mips_multi_num_insns;
+ }
+ return cfun->machine->load_label_length;
+}
+
+/* Emit an asm sequence to start a noat block and load the address
+ of a label into $1. */
+
+void
+mips_output_load_label (rtx target)
+{
+ mips_push_asm_switch (&mips_noat);
+ if (TARGET_EXPLICIT_RELOCS)
+ {
+ mips_process_load_label (target);
+ mips_multi_write ();
+ }
+ else
+ {
+ if (Pmode == DImode)
+ output_asm_insn ("dla\t%@,%0", &target);
+ else
+ output_asm_insn ("la\t%@,%0", &target);
+ }
+}
+
/* Return the length of INSN. LENGTH is the initial length computed by
attributes in the machine-description file. */
int
mips_adjust_insn_length (rtx insn, int length)
{
+ /* mips.md uses MAX_PIC_BRANCH_LENGTH as a placeholder for the length
+ of a PIC long-branch sequence. Substitute the correct value. */
+ if (length == MAX_PIC_BRANCH_LENGTH
+ && INSN_CODE (insn) >= 0
+ && get_attr_type (insn) == TYPE_BRANCH)
+ {
+ /* Add the branch-over instruction and its delay slot, if this
+ is a conditional branch. */
+ length = simplejump_p (insn) ? 0 : 8;
+
+ /* Load the label into $AT and jump to it. Ignore the delay
+ slot of the jump. */
+ length += mips_load_label_length () + 4;
+ }
+
/* A unconditional jump has an unfilled delay slot if it is not part
of a sequence. A conditional jump normally has a delay slot, but
does not on MIPS16. */
@@ -10769,38 +11170,9 @@ mips_adjust_insn_length (rtx insn, int length)
return length;
}
-/* Return an asm sequence to start a noat block and load the address
- of a label into $1. */
-
-const char *
-mips_output_load_label (void)
-{
- if (TARGET_EXPLICIT_RELOCS)
- switch (mips_abi)
- {
- case ABI_N32:
- return "%[lw\t%@,%%got_page(%0)(%+)\n\taddiu\t%@,%@,%%got_ofst(%0)";
-
- case ABI_64:
- return "%[ld\t%@,%%got_page(%0)(%+)\n\tdaddiu\t%@,%@,%%got_ofst(%0)";
-
- default:
- if (ISA_HAS_LOAD_DELAY)
- return "%[lw\t%@,%%got(%0)(%+)%#\n\taddiu\t%@,%@,%%lo(%0)";
- return "%[lw\t%@,%%got(%0)(%+)\n\taddiu\t%@,%@,%%lo(%0)";
- }
- else
- {
- if (Pmode == DImode)
- return "%[dla\t%@,%0";
- else
- return "%[la\t%@,%0";
- }
-}
-
/* Return the assembly code for INSN, which has the operands given by
- OPERANDS, and which branches to OPERANDS[1] if some condition is true.
- BRANCH_IF_TRUE is the asm template that should be used if OPERANDS[1]
+ OPERANDS, and which branches to OPERANDS[0] if some condition is true.
+ BRANCH_IF_TRUE is the asm template that should be used if OPERANDS[0]
is in range of a direct branch. BRANCH_IF_FALSE is an inverted
version of BRANCH_IF_TRUE. */
@@ -10812,7 +11184,7 @@ mips_output_conditional_branch (rtx insn, rtx *operands,
unsigned int length;
rtx taken, not_taken;
- gcc_assert (LABEL_P (operands[1]));
+ gcc_assert (LABEL_P (operands[0]));
length = get_attr_length (insn);
if (length <= 8)
@@ -10826,10 +11198,10 @@ mips_output_conditional_branch (rtx insn, rtx *operands,
not use branch-likely instructions. */
mips_branch_likely = false;
not_taken = gen_label_rtx ();
- taken = operands[1];
+ taken = operands[0];
/* Generate the reversed branch to NOT_TAKEN. */
- operands[1] = not_taken;
+ operands[0] = not_taken;
output_asm_insn (branch_if_false, operands);
/* If INSN has a delay slot, we must provide delay slots for both the
@@ -10851,11 +11223,11 @@ mips_output_conditional_branch (rtx insn, rtx *operands,
}
/* Output the unconditional branch to TAKEN. */
- if (length <= 16)
- output_asm_insn ("j\t%0%/", &taken);
+ if (TARGET_ABSOLUTE_JUMPS)
+ output_asm_insn (MIPS_ABSOLUTE_JUMP ("j\t%0%/"), &taken);
else
{
- output_asm_insn (mips_output_load_label (), &taken);
+ mips_output_load_label (taken);
output_asm_insn ("jr\t%@%]%/", 0);
}
@@ -10881,10 +11253,10 @@ mips_output_conditional_branch (rtx insn, rtx *operands,
return "";
}
-/* Return the assembly code for INSN, which branches to OPERANDS[1]
+/* Return the assembly code for INSN, which branches to OPERANDS[0]
if some ordering condition is true. The condition is given by
- OPERANDS[0] if !INVERTED_P, otherwise it is the inverse of
- OPERANDS[0]. OPERANDS[2] is the comparison's first operand;
+ OPERANDS[1] if !INVERTED_P, otherwise it is the inverse of
+ OPERANDS[1]. OPERANDS[2] is the comparison's first operand;
its second is always zero. */
const char *
@@ -10892,17 +11264,17 @@ mips_output_order_conditional_branch (rtx insn, rtx *operands, bool inverted_p)
{
const char *branch[2];
- /* Make BRANCH[1] branch to OPERANDS[1] when the condition is true.
+ /* Make BRANCH[1] branch to OPERANDS[0] when the condition is true.
Make BRANCH[0] branch on the inverse condition. */
- switch (GET_CODE (operands[0]))
+ switch (GET_CODE (operands[1]))
{
/* These cases are equivalent to comparisons against zero. */
case LEU:
inverted_p = !inverted_p;
/* Fall through. */
case GTU:
- branch[!inverted_p] = MIPS_BRANCH ("bne", "%2,%.,%1");
- branch[inverted_p] = MIPS_BRANCH ("beq", "%2,%.,%1");
+ branch[!inverted_p] = MIPS_BRANCH ("bne", "%2,%.,%0");
+ branch[inverted_p] = MIPS_BRANCH ("beq", "%2,%.,%0");
break;
/* These cases are always true or always false. */
@@ -10910,13 +11282,13 @@ mips_output_order_conditional_branch (rtx insn, rtx *operands, bool inverted_p)
inverted_p = !inverted_p;
/* Fall through. */
case GEU:
- branch[!inverted_p] = MIPS_BRANCH ("beq", "%.,%.,%1");
- branch[inverted_p] = MIPS_BRANCH ("bne", "%.,%.,%1");
+ branch[!inverted_p] = MIPS_BRANCH ("beq", "%.,%.,%0");
+ branch[inverted_p] = MIPS_BRANCH ("bne", "%.,%.,%0");
break;
default:
- branch[!inverted_p] = MIPS_BRANCH ("b%C0z", "%2,%1");
- branch[inverted_p] = MIPS_BRANCH ("b%N0z", "%2,%1");
+ branch[!inverted_p] = MIPS_BRANCH ("b%C1z", "%2,%0");
+ branch[inverted_p] = MIPS_BRANCH ("b%N1z", "%2,%0");
break;
}
return mips_output_conditional_branch (insn, operands, branch[1], branch[0]);
@@ -11915,7 +12287,8 @@ mips_variable_issue (FILE *file ATTRIBUTE_UNUSED, int verbose ATTRIBUTE_UNUSED,
/* Ignore USEs and CLOBBERs; don't count them against the issue rate. */
if (USEFUL_INSN_P (insn))
{
- more--;
+ if (get_attr_type (insn) != TYPE_GHOST)
+ more--;
if (!reload_completed && TUNE_MACC_CHAINS)
mips_macc_chains_record (insn);
vr4130_last_insn = insn;
@@ -14173,6 +14546,46 @@ mips_reorg_process_insns (void)
htab_delete (htab);
}
+/* If we are using a GOT, but have not decided to use a global pointer yet,
+ see whether we need one to implement long branches. Convert the ghost
+ global-pointer instructions into real ones if so. */
+
+static bool
+mips_expand_ghost_gp_insns (void)
+{
+ rtx insn;
+ int normal_length;
+
+ /* Quick exit if we already know that we will or won't need a
+ global pointer. */
+ if (!TARGET_USE_GOT
+ || cfun->machine->global_pointer == INVALID_REGNUM
+ || mips_must_initialize_gp_p ())
+ return false;
+
+ shorten_branches (get_insns ());
+
+ /* Look for a branch that is longer than normal. The normal length for
+ non-MIPS16 branches is 8, because the length includes the delay slot.
+ It is 4 for MIPS16, because MIPS16 branches are extended instructions,
+ but they have no delay slot. */
+ normal_length = (TARGET_MIPS16 ? 4 : 8);
+ for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
+ if (JUMP_P (insn)
+ && USEFUL_INSN_P (insn)
+ && get_attr_length (insn) > normal_length)
+ break;
+
+ if (insn == NULL_RTX)
+ return false;
+
+ /* We've now established that we need $gp. */
+ cfun->machine->must_initialize_gp_p = true;
+ split_all_insns_noflow ();
+
+ return true;
+}
+
/* Implement TARGET_MACHINE_DEPENDENT_REORG. */
static void
@@ -14189,6 +14602,10 @@ mips_reorg (void)
&& TUNE_MIPS4130
&& TARGET_VR4130_ALIGN)
vr4130_align_insns ();
+ if (mips_expand_ghost_gp_insns ())
+ /* The expansion could invalidate some of the VR4130 alignment
+ optimizations, but this should be an extremely rare case anyhow. */
+ mips_reorg_process_insns ();
}
/* Implement TARGET_ASM_OUTPUT_MI_THUNK. Generate rtl rather than asm text
@@ -14222,6 +14639,7 @@ mips_output_mi_thunk (FILE *file, tree thunk_fndecl ATTRIBUTE_UNUSED,
TARGET_CALL_SAVED_GP. */
cfun->machine->global_pointer
= TARGET_CALL_SAVED_GP ? 15 : GLOBAL_POINTER_REGNUM;
+ cfun->machine->must_initialize_gp_p = true;
SET_REGNO (pic_offset_table_rtx, cfun->machine->global_pointer);
/* Set up the global pointer for n32 or n64 abicalls. */
@@ -15137,6 +15555,31 @@ mips_order_regs_for_local_alloc (void)
}
}
+/* Implement EH_USES. */
+
+bool
+mips_eh_uses (unsigned int regno)
+{
+ if (reload_completed && !TARGET_ABSOLUTE_JUMPS)
+ {
+ /* We need to force certain registers to be live in order to handle
+ PIC long branches correctly. See mips_must_initialize_gp_p for
+ details. */
+ if (mips_cfun_has_cprestore_slot_p ())
+ {
+ if (regno == CPRESTORE_SLOT_REGNUM)
+ return true;
+ }
+ else
+ {
+ if (cfun->machine->global_pointer == regno)
+ return true;
+ }
+ }
+
+ return false;
+}
+
/* Implement EPILOGUE_USES. */
bool
diff --git a/gcc/config/mips/mips.h b/gcc/config/mips/mips.h
index 352dbd25618..eda74479ed2 100644
--- a/gcc/config/mips/mips.h
+++ b/gcc/config/mips/mips.h
@@ -161,10 +161,13 @@ enum mips_code_readable_setting {
/* True if the call patterns should be split into a jalr followed by
an instruction to restore $gp. It is only safe to split the load
- from the call when every use of $gp is explicit. */
+ from the call when every use of $gp is explicit.
+
+ See mips_must_initialize_gp_p for details about how we manage the
+ global pointer. */
#define TARGET_SPLIT_CALLS \
- (TARGET_EXPLICIT_RELOCS && TARGET_CALL_CLOBBERED_GP)
+ (TARGET_EXPLICIT_RELOCS && TARGET_CALL_CLOBBERED_GP && epilogue_completed)
/* True if we're generating a form of -mabicalls in which we can use
operators like %hi and %lo to refer to locally-binding symbols.
@@ -202,6 +205,17 @@ enum mips_code_readable_setting {
/* True if TARGET_USE_GOT and if $gp is a call-saved register. */
#define TARGET_CALL_SAVED_GP (TARGET_USE_GOT && !TARGET_CALL_CLOBBERED_GP)
+/* True if we should use .cprestore to store to the cprestore slot.
+
+ We continue to use .cprestore for explicit-reloc code so that JALs
+ inside inline asms will work correctly. */
+#define TARGET_CPRESTORE_DIRECTIVE \
+ (TARGET_ABICALLS_PIC2 && !TARGET_MIPS16)
+
+/* True if we can use the J and JAL instructions. */
+#define TARGET_ABSOLUTE_JUMPS \
+ (!flag_pic || TARGET_ABSOLUTE_ABICALLS)
+
/* True if indirect calls must use register class PIC_FN_ADDR_REG.
This is true for both the PIC and non-PIC VxWorks RTP modes. */
#define TARGET_USE_PIC_FN_ADDR_REG (TARGET_ABICALLS || TARGET_VXWORKS_RTP)
@@ -1300,6 +1314,8 @@ enum mips_code_readable_setting {
#define EH_RETURN_STACKADJ_RTX gen_rtx_REG (Pmode, GP_REG_FIRST + 3)
+#define EH_USES(N) mips_eh_uses (N)
+
/* Offsets recorded in opcodes are a multiple of this alignment factor.
The default for this in 64-bit mode is 8, which causes problems with
SFmode register saves. */
@@ -1543,11 +1559,12 @@ enum mips_code_readable_setting {
- 8 condition code registers
- 2 accumulator registers (hi and lo)
- 32 registers each for coprocessors 0, 2 and 3
- - 3 fake registers:
+ - 4 fake registers:
- ARG_POINTER_REGNUM
- FRAME_POINTER_REGNUM
- GOT_VERSION_REGNUM (see the comment above load_call<mode> for details)
- - 3 dummy entries that were used at various times in the past.
+ - CPRESTORE_SLOT_REGNUM
+ - 2 dummy entries that were used at various times in the past.
- 6 DSP accumulator registers (3 hi-lo pairs) for MIPS DSP ASE
- 6 DSP control registers */
@@ -2661,6 +2678,13 @@ typedef struct mips_args {
#define MIPS_BRANCH(OPCODE, OPERANDS) \
"%*" OPCODE "%?\t" OPERANDS "%/"
+/* Return an asm string that forces INSN to be treated as an absolute
+ J or JAL instruction instead of an assembler macro. */
+#define MIPS_ABSOLUTE_JUMP(INSN) \
+ (TARGET_ABICALLS_PIC2 \
+ ? ".option\tpic0\n\t" INSN "\n\t.option\tpic2" \
+ : INSN)
+
/* Return the asm template for a call. INSN is the instruction's mnemonic
("j" or "jal"), OPERANDS are its operands, and OPNO is the operand number
of the target.
@@ -2675,11 +2699,7 @@ typedef struct mips_args {
? "%*" INSN "\t%" #OPNO "%/" \
: REG_P (OPERANDS[OPNO]) \
? "%*" INSN "r\t%" #OPNO "%/" \
- : TARGET_ABICALLS_PIC2 \
- ? (".option\tpic0\n\t" \
- "%*" INSN "\t%" #OPNO "%/\n\t" \
- ".option\tpic2") \
- : "%*" INSN "\t%" #OPNO "%/")
+ : MIPS_ABSOLUTE_JUMP ("%*" INSN "\t%" #OPNO "%/"))
/* Control the assembler format that we output. */
@@ -2707,7 +2727,7 @@ typedef struct mips_args {
"$f16", "$f17", "$f18", "$f19", "$f20", "$f21", "$f22", "$f23", \
"$f24", "$f25", "$f26", "$f27", "$f28", "$f29", "$f30", "$f31", \
"hi", "lo", "", "$fcc0","$fcc1","$fcc2","$fcc3","$fcc4", \
- "$fcc5","$fcc6","$fcc7","", "", "$arg", "$frame", "$fakec", \
+ "$fcc5","$fcc6","$fcc7","", "$cprestore", "$arg", "$frame", "$fakec", \
"$c0r0", "$c0r1", "$c0r2", "$c0r3", "$c0r4", "$c0r5", "$c0r6", "$c0r7", \
"$c0r8", "$c0r9", "$c0r10","$c0r11","$c0r12","$c0r13","$c0r14","$c0r15", \
"$c0r16","$c0r17","$c0r18","$c0r19","$c0r20","$c0r21","$c0r22","$c0r23", \
diff --git a/gcc/config/mips/mips.md b/gcc/config/mips/mips.md
index a510e0a7b74..4e1e7852c71 100644
--- a/gcc/config/mips/mips.md
+++ b/gcc/config/mips/mips.md
@@ -29,11 +29,13 @@
(UNSPEC_STORE_WORD 2)
(UNSPEC_GET_FNADDR 3)
(UNSPEC_BLOCKAGE 4)
- (UNSPEC_CPRESTORE 5)
- (UNSPEC_RESTORE_GP 6)
- (UNSPEC_EH_RETURN 7)
- (UNSPEC_CONSTTABLE_INT 8)
- (UNSPEC_CONSTTABLE_FLOAT 9)
+ (UNSPEC_POTENTIAL_CPRESTORE 5)
+ (UNSPEC_CPRESTORE 6)
+ (UNSPEC_RESTORE_GP 7)
+ (UNSPEC_MOVE_GP 8)
+ (UNSPEC_EH_RETURN 9)
+ (UNSPEC_CONSTTABLE_INT 10)
+ (UNSPEC_CONSTTABLE_FLOAT 11)
(UNSPEC_ALIGN 14)
(UNSPEC_HIGH 17)
(UNSPEC_LOAD_LEFT 18)
@@ -77,6 +79,7 @@
(UNSPEC_ADDRESS_FIRST 100)
(TLS_GET_TP_REGNUM 3)
+ (CPRESTORE_SLOT_REGNUM 76)
(GOT_VERSION_REGNUM 79)
;; For MIPS Paired-Singled Floating Point Instructions.
@@ -256,6 +259,9 @@
(UNSPEC_MIPS_CACHE 600)
(UNSPEC_R10K_CACHE_BARRIER 601)
+
+ ;; PIC long branch sequences are never longer than 100 bytes.
+ (MAX_PIC_BRANCH_LENGTH 100)
]
)
@@ -281,12 +287,11 @@
;;
;; jal is always a macro for TARGET_CALL_CLOBBERED_GP because it includes
;; an instruction to restore $gp. Direct jals are also macros for
-;; flag_pic && !TARGET_ABSOLUTE_ABICALLS because they first load
-;; the target address into a register.
+;; !TARGET_ABSOLUTE_JUMPS because they first load the target address
+;; into a register.
(define_attr "jal_macro" "no,yes"
(cond [(eq_attr "jal" "direct")
- (symbol_ref "((TARGET_CALL_CLOBBERED_GP
- || (flag_pic && !TARGET_ABSOLUTE_ABICALLS))
+ (symbol_ref "(TARGET_CALL_CLOBBERED_GP || !TARGET_ABSOLUTE_JUMPS
? JAL_MACRO_YES : JAL_MACRO_NO)")
(eq_attr "jal" "indirect")
(symbol_ref "(TARGET_CALL_CLOBBERED_GP
@@ -498,9 +503,10 @@
(ne (symbol_ref "TARGET_MIPS16") (const_int 0)))
(const_int 8)
- ;; Direct branch instructions have a range of [-0x40000,0x3fffc].
- ;; If a branch is outside this range, we have a choice of two
- ;; sequences. For PIC, an out-of-range branch like:
+ ;; Direct branch instructions have a range of [-0x20000,0x1fffc],
+ ;; relative to the address of the delay slot. If a branch is
+ ;; outside this range, we have a choice of two sequences.
+ ;; For PIC, an out-of-range branch like:
;;
;; bne r1,r2,target
;; dslot
@@ -514,9 +520,6 @@
;; nop
;; 1:
;;
- ;; where the load address can be up to three instructions long
- ;; (lw, nop, addiu).
- ;;
;; The non-PIC case is similar except that we use a direct
;; jump instead of an la/jr pair. Since the target of this
;; jump is an absolute 28-bit bit address (the other bits
@@ -531,12 +534,21 @@
;; will add the length of the implicit nop. The values for
;; forward and backward branches will be different as well.
(eq_attr "type" "branch")
- (cond [(and (le (minus (match_dup 1) (pc)) (const_int 131064))
- (le (minus (pc) (match_dup 1)) (const_int 131068)))
- (const_int 4)
- (ne (symbol_ref "flag_pic") (const_int 0))
- (const_int 24)
- ] (const_int 12))
+ (cond [(and (le (minus (match_dup 0) (pc)) (const_int 131064))
+ (le (minus (pc) (match_dup 0)) (const_int 131068)))
+ (const_int 4)
+
+ ;; The non-PIC case: branch, first delay slot, and J.
+ (ne (symbol_ref "TARGET_ABSOLUTE_JUMPS") (const_int 0))
+ (const_int 12)]
+
+ ;; Use MAX_PIC_BRANCH_LENGTH as a (gross) overestimate.
+ ;; mips_adjust_insn_length substitutes the correct length.
+ ;;
+ ;; Note that we can't simply use (symbol_ref ...) here
+ ;; because genattrtab needs to know the maximum length
+ ;; of an insn.
+ (const_int MAX_PIC_BRANCH_LENGTH))
;; "Ghost" instructions occupy no space.
(eq_attr "type" "ghost")
@@ -4754,12 +4766,12 @@
;; function address.
(define_insn_and_split "loadgp_newabi_<mode>"
[(set (match_operand:P 0 "register_operand" "=d")
- (unspec_volatile:P [(match_operand:P 1)
- (match_operand:P 2 "register_operand" "d")]
- UNSPEC_LOADGP))]
+ (unspec:P [(match_operand:P 1)
+ (match_operand:P 2 "register_operand" "d")]
+ UNSPEC_LOADGP))]
"mips_current_loadgp_style () == LOADGP_NEWABI"
- "#"
- ""
+ { return mips_must_initialize_gp_p () ? "#" : ""; }
+ "&& mips_must_initialize_gp_p ()"
[(set (match_dup 0) (match_dup 3))
(set (match_dup 0) (match_dup 4))
(set (match_dup 0) (match_dup 5))]
@@ -4768,21 +4780,21 @@
operands[4] = gen_rtx_PLUS (Pmode, operands[0], operands[2]);
operands[5] = gen_rtx_LO_SUM (Pmode, operands[0], operands[1]);
}
- [(set_attr "length" "12")])
+ [(set_attr "type" "ghost")])
;; Likewise, for -mno-shared code. Operand 0 is the __gnu_local_gp symbol.
(define_insn_and_split "loadgp_absolute_<mode>"
[(set (match_operand:P 0 "register_operand" "=d")
- (unspec_volatile:P [(match_operand:P 1)] UNSPEC_LOADGP))]
+ (unspec:P [(match_operand:P 1)] UNSPEC_LOADGP))]
"mips_current_loadgp_style () == LOADGP_ABSOLUTE"
- "#"
- ""
+ { return mips_must_initialize_gp_p () ? "#" : ""; }
+ "&& mips_must_initialize_gp_p ()"
[(const_int 0)]
{
mips_emit_move (operands[0], operands[1]);
DONE;
}
- [(set_attr "length" "8")])
+ [(set_attr "type" "ghost")])
;; This blockage instruction prevents the gp load from being
;; scheduled after an implicit use of gp. It also prevents
@@ -4791,19 +4803,18 @@
[(unspec_volatile [(reg:SI 28)] UNSPEC_BLOCKAGE)]
""
""
- [(set_attr "type" "ghost")
- (set_attr "mode" "none")])
+ [(set_attr "type" "ghost")])
;; Initialize $gp for RTP PIC. Operand 0 is the __GOTT_BASE__ symbol
;; and operand 1 is the __GOTT_INDEX__ symbol.
(define_insn_and_split "loadgp_rtp_<mode>"
[(set (match_operand:P 0 "register_operand" "=d")
- (unspec_volatile:P [(match_operand:P 1 "symbol_ref_operand")
- (match_operand:P 2 "symbol_ref_operand")]
- UNSPEC_LOADGP))]
+ (unspec:P [(match_operand:P 1 "symbol_ref_operand")
+ (match_operand:P 2 "symbol_ref_operand")]
+ UNSPEC_LOADGP))]
"mips_current_loadgp_style () == LOADGP_RTP"
- "#"
- ""
+ { return mips_must_initialize_gp_p () ? "#" : ""; }
+ "&& mips_must_initialize_gp_p ()"
[(set (match_dup 0) (high:P (match_dup 3)))
(set (match_dup 0) (unspec:P [(match_dup 0)
(match_dup 3)] UNSPEC_LOAD_GOT))
@@ -4813,37 +4824,72 @@
operands[3] = mips_unspec_address (operands[1], SYMBOL_ABSOLUTE);
operands[4] = mips_unspec_address (operands[2], SYMBOL_HALF);
}
- [(set_attr "length" "12")])
+ [(set_attr "type" "ghost")])
;; Initialize the global pointer for MIPS16 code. Operand 0 is the
;; global pointer and operand 1 is the MIPS16 register that holds
;; the required value.
(define_insn_and_split "copygp_mips16"
[(set (match_operand:SI 0 "register_operand" "=y")
- (unspec_volatile:SI [(match_operand:SI 1 "register_operand" "d")]
- UNSPEC_COPYGP))]
+ (unspec:SI [(match_operand:SI 1 "register_operand" "d")]
+ UNSPEC_COPYGP))]
"TARGET_MIPS16"
- "#"
- "&& reload_completed"
- [(set (match_dup 0) (match_dup 1))])
+ { return mips_must_initialize_gp_p () ? "#" : ""; }
+ "&& mips_must_initialize_gp_p ()"
+ [(set (match_dup 0) (match_dup 1))]
+ ""
+ [(set_attr "type" "ghost")])
+
+;; A placeholder for where the cprestore instruction should go,
+;; if we decide we need one. Operand 0 and operand 1 are as for
+;; "cprestore". Operand 2 is a register that holds the gp value.
+;;
+;; The "cprestore" pattern requires operand 2 to be pic_offset_table_rtx,
+;; otherwise any register that holds the correct value will do.
+(define_insn_and_split "potential_cprestore"
+ [(set (match_operand:SI 0 "cprestore_save_slot_operand" "=X,X")
+ (unspec:SI [(match_operand:SI 1 "const_int_operand" "I,i")
+ (match_operand:SI 2 "register_operand" "d,d")]
+ UNSPEC_POTENTIAL_CPRESTORE))
+ (clobber (match_operand:SI 3 "scratch_operand" "=X,&d"))]
+ "!TARGET_CPRESTORE_DIRECTIVE || operands[2] == pic_offset_table_rtx"
+ { return mips_must_initialize_gp_p () ? "#" : ""; }
+ "mips_must_initialize_gp_p ()"
+ [(const_int 0)]
+{
+ mips_save_gp_to_cprestore_slot (operands[0], operands[1],
+ operands[2], operands[3]);
+ DONE;
+}
+ [(set_attr "type" "ghost")])
;; Emit a .cprestore directive, which normally expands to a single store
-;; instruction. Note that we continue to use .cprestore for explicit reloc
-;; code so that jals inside inline asms will work correctly.
+;; instruction. Operand 0 is a (possibly illegitimate) sp-based MEM
+;; for the cprestore slot. Operand 1 is the offset of the slot from
+;; the stack pointer. (This is redundant with operand 0, but it makes
+;; things a little simpler.)
(define_insn "cprestore"
- [(unspec_volatile [(match_operand 0 "const_int_operand" "I,i")
- (use (reg:SI 28))]
- UNSPEC_CPRESTORE)]
- ""
+ [(set (match_operand:SI 0 "cprestore_save_slot_operand" "=X,X")
+ (unspec:SI [(match_operand:SI 1 "const_int_operand" "I,i")
+ (reg:SI 28)]
+ UNSPEC_CPRESTORE))]
+ "TARGET_CPRESTORE_DIRECTIVE"
{
if (mips_nomacro.nesting_level > 0 && which_alternative == 1)
- return ".set\tmacro\;.cprestore\t%0\;.set\tnomacro";
+ return ".set\tmacro\;.cprestore\t%1\;.set\tnomacro";
else
- return ".cprestore\t%0";
+ return ".cprestore\t%1";
}
[(set_attr "type" "store")
(set_attr "length" "4,12")])
+(define_insn "use_cprestore"
+ [(set (reg:SI CPRESTORE_SLOT_REGNUM)
+ (match_operand:SI 0 "cprestore_load_slot_operand"))]
+ ""
+ ""
+ [(set_attr "type" "ghost")])
+
;; Expand in-line code to clear the instruction cache between operand[0] and
;; operand[1].
(define_expand "clear_cache"
@@ -5149,100 +5195,94 @@
(define_insn "*branch_fp"
[(set (pc)
(if_then_else
- (match_operator 0 "equality_operator"
+ (match_operator 1 "equality_operator"
[(match_operand:CC 2 "register_operand" "z")
(const_int 0)])
- (label_ref (match_operand 1 "" ""))
+ (label_ref (match_operand 0 "" ""))
(pc)))]
"TARGET_HARD_FLOAT"
{
return mips_output_conditional_branch (insn, operands,
- MIPS_BRANCH ("b%F0", "%Z2%1"),
- MIPS_BRANCH ("b%W0", "%Z2%1"));
+ MIPS_BRANCH ("b%F1", "%Z2%0"),
+ MIPS_BRANCH ("b%W1", "%Z2%0"));
}
- [(set_attr "type" "branch")
- (set_attr "mode" "none")])
+ [(set_attr "type" "branch")])
(define_insn "*branch_fp_inverted"
[(set (pc)
(if_then_else
- (match_operator 0 "equality_operator"
+ (match_operator 1 "equality_operator"
[(match_operand:CC 2 "register_operand" "z")
(const_int 0)])
(pc)
- (label_ref (match_operand 1 "" ""))))]
+ (label_ref (match_operand 0 "" ""))))]
"TARGET_HARD_FLOAT"
{
return mips_output_conditional_branch (insn, operands,
- MIPS_BRANCH ("b%W0", "%Z2%1"),
- MIPS_BRANCH ("b%F0", "%Z2%1"));
+ MIPS_BRANCH ("b%W1", "%Z2%0"),
+ MIPS_BRANCH ("b%F1", "%Z2%0"));
}
- [(set_attr "type" "branch")
- (set_attr "mode" "none")])
+ [(set_attr "type" "branch")])
;; Conditional branches on ordered comparisons with zero.
(define_insn "*branch_order<mode>"
[(set (pc)
(if_then_else
- (match_operator 0 "order_operator"
+ (match_operator 1 "order_operator"
[(match_operand:GPR 2 "register_operand" "d")
(const_int 0)])
- (label_ref (match_operand 1 "" ""))
+ (label_ref (match_operand 0 "" ""))
(pc)))]
"!TARGET_MIPS16"
{ return mips_output_order_conditional_branch (insn, operands, false); }
- [(set_attr "type" "branch")
- (set_attr "mode" "none")])
+ [(set_attr "type" "branch")])
(define_insn "*branch_order<mode>_inverted"
[(set (pc)
(if_then_else
- (match_operator 0 "order_operator"
+ (match_operator 1 "order_operator"
[(match_operand:GPR 2 "register_operand" "d")
(const_int 0)])
(pc)
- (label_ref (match_operand 1 "" ""))))]
+ (label_ref (match_operand 0 "" ""))))]
"!TARGET_MIPS16"
{ return mips_output_order_conditional_branch (insn, operands, true); }
- [(set_attr "type" "branch")
- (set_attr "mode" "none")])
+ [(set_attr "type" "branch")])
;; Conditional branch on equality comparison.
(define_insn "*branch_equality<mode>"
[(set (pc)
(if_then_else
- (match_operator 0 "equality_operator"
+ (match_operator 1 "equality_operator"
[(match_operand:GPR 2 "register_operand" "d")
(match_operand:GPR 3 "reg_or_0_operand" "dJ")])
- (label_ref (match_operand 1 "" ""))
+ (label_ref (match_operand 0 "" ""))
(pc)))]
"!TARGET_MIPS16"
{
return mips_output_conditional_branch (insn, operands,
- MIPS_BRANCH ("b%C0", "%2,%z3,%1"),
- MIPS_BRANCH ("b%N0", "%2,%z3,%1"));
+ MIPS_BRANCH ("b%C1", "%2,%z3,%0"),
+ MIPS_BRANCH ("b%N1", "%2,%z3,%0"));
}
- [(set_attr "type" "branch")
- (set_attr "mode" "none")])
+ [(set_attr "type" "branch")])
(define_insn "*branch_equality<mode>_inverted"
[(set (pc)
(if_then_else
- (match_operator 0 "equality_operator"
+ (match_operator 1 "equality_operator"
[(match_operand:GPR 2 "register_operand" "d")
(match_operand:GPR 3 "reg_or_0_operand" "dJ")])
(pc)
- (label_ref (match_operand 1 "" ""))))]
+ (label_ref (match_operand 0 "" ""))))]
"!TARGET_MIPS16"
{
return mips_output_conditional_branch (insn, operands,
- MIPS_BRANCH ("b%N0", "%2,%z3,%1"),
- MIPS_BRANCH ("b%C0", "%2,%z3,%1"));
+ MIPS_BRANCH ("b%N1", "%2,%z3,%0"),
+ MIPS_BRANCH ("b%C1", "%2,%z3,%0"));
}
- [(set_attr "type" "branch")
- (set_attr "mode" "none")])
+ [(set_attr "type" "branch")])
;; MIPS16 branches
@@ -5271,8 +5311,7 @@
return "bt%N0z\t%3";
}
}
- [(set_attr "type" "branch")
- (set_attr "mode" "none")])
+ [(set_attr "type" "branch")])
(define_expand "cbranch<mode>4"
[(set (pc)
@@ -5313,42 +5352,40 @@
[(set (pc)
(if_then_else
(equality_op (zero_extract:GPR
- (match_operand:GPR 0 "register_operand" "d")
+ (match_operand:GPR 1 "register_operand" "d")
(const_int 1)
(match_operand 2 "const_int_operand" ""))
(const_int 0))
- (label_ref (match_operand 1 ""))
+ (label_ref (match_operand 0 ""))
(pc)))]
"ISA_HAS_BBIT && UINTVAL (operands[2]) < GET_MODE_BITSIZE (<MODE>mode)"
{
return
mips_output_conditional_branch (insn, operands,
- MIPS_BRANCH ("bbit<bbv>", "%0,%2,%1"),
- MIPS_BRANCH ("bbit<bbinv>", "%0,%2,%1"));
+ MIPS_BRANCH ("bbit<bbv>", "%1,%2,%0"),
+ MIPS_BRANCH ("bbit<bbinv>", "%1,%2,%0"));
}
[(set_attr "type" "branch")
- (set_attr "mode" "none")
(set_attr "branch_likely" "no")])
(define_insn "*branch_bit<bbv><mode>_inverted"
[(set (pc)
(if_then_else
(equality_op (zero_extract:GPR
- (match_operand:GPR 0 "register_operand" "d")
+ (match_operand:GPR 1 "register_operand" "d")
(const_int 1)
(match_operand 2 "const_int_operand" ""))
(const_int 0))
(pc)
- (label_ref (match_operand 1 ""))))]
+ (label_ref (match_operand 0 ""))))]
"ISA_HAS_BBIT && UINTVAL (operands[2]) < GET_MODE_BITSIZE (<MODE>mode)"
{
return
mips_output_conditional_branch (insn, operands,
- MIPS_BRANCH ("bbit<bbinv>", "%0,%2,%1"),
- MIPS_BRANCH ("bbit<bbv>", "%0,%2,%1"));
+ MIPS_BRANCH ("bbit<bbinv>", "%1,%2,%0"),
+ MIPS_BRANCH ("bbit<bbv>", "%1,%2,%0"));
}
[(set_attr "type" "branch")
- (set_attr "mode" "none")
(set_attr "branch_likely" "no")])
;;
@@ -5535,47 +5572,41 @@
;; Unconditional branches.
-(define_insn "jump"
+(define_expand "jump"
[(set (pc)
- (label_ref (match_operand 0 "" "")))]
- "!TARGET_MIPS16"
+ (label_ref (match_operand 0)))])
+
+(define_insn "*jump_absolute"
+ [(set (pc)
+ (label_ref (match_operand 0)))]
+ "!TARGET_MIPS16 && TARGET_ABSOLUTE_JUMPS"
+ { return MIPS_ABSOLUTE_JUMP ("%*j\t%l0%/"); }
+ [(set_attr "type" "jump")])
+
+(define_insn "*jump_pic"
+ [(set (pc)
+ (label_ref (match_operand 0)))]
+ "!TARGET_MIPS16 && !TARGET_ABSOLUTE_JUMPS"
{
- if (flag_pic)
+ if (get_attr_length (insn) <= 8)
+ return "%*b\t%l0%/";
+ else
{
- if (get_attr_length (insn) <= 8)
- return "%*b\t%l0%/";
- else
- {
- output_asm_insn (mips_output_load_label (), operands);
- return "%*jr\t%@%/%]";
- }
+ mips_output_load_label (operands[0]);
+ return "%*jr\t%@%/%]";
}
- else
- return "%*j\t%l0%/";
}
- [(set_attr "type" "jump")
- (set_attr "mode" "none")
- (set (attr "length")
- ;; We can't use `j' when emitting PIC. Emit a branch if it's
- ;; in range, otherwise load the address of the branch target into
- ;; $at and then jump to it.
- (if_then_else
- (ior (eq (symbol_ref "flag_pic") (const_int 0))
- (lt (abs (minus (match_dup 0)
- (plus (pc) (const_int 4))))
- (const_int 131072)))
- (const_int 4) (const_int 16)))])
+ [(set_attr "type" "branch")])
;; We need a different insn for the mips16, because a mips16 branch
;; does not have a delay slot.
-(define_insn ""
+(define_insn "*jump_mips16"
[(set (pc)
(label_ref (match_operand 0 "" "")))]
"TARGET_MIPS16"
"b\t%l0"
- [(set_attr "type" "branch")
- (set_attr "mode" "none")])
+ [(set_attr "type" "branch")])
(define_expand "indirect_jump"
[(set (pc) (match_operand 0 "register_operand"))]
@@ -5876,14 +5907,28 @@
(clobber (match_scratch:SI 0 "=&d"))]
"TARGET_CALL_CLOBBERED_GP"
"#"
- "&& reload_completed"
+ "&& epilogue_completed"
[(const_int 0)]
{
- mips_restore_gp (operands[0]);
+ mips_restore_gp_from_cprestore_slot (operands[0]);
DONE;
}
- [(set_attr "type" "load")
- (set_attr "length" "12")])
+ [(set_attr "type" "ghost")])
+
+;; Move between $gp and its register save slot.
+(define_insn_and_split "move_gp<mode>"
+ [(set (match_operand:GPR 0 "nonimmediate_operand" "=d,m")
+ (unspec:GPR [(match_operand:GPR 1 "move_operand" "m,d")]
+ UNSPEC_MOVE_GP))]
+ ""
+ { return mips_must_initialize_gp_p () ? "#" : ""; }
+ "mips_must_initialize_gp_p ()"
+ [(const_int 0)]
+{
+ mips_emit_move (operands[0], operands[1]);
+ DONE;
+}
+ [(set_attr "type" "ghost")])
;;
;; ....................
diff --git a/gcc/config/mips/predicates.md b/gcc/config/mips/predicates.md
index a9a0177197e..e1cb4573688 100644
--- a/gcc/config/mips/predicates.md
+++ b/gcc/config/mips/predicates.md
@@ -244,6 +244,14 @@
}
})
+(define_predicate "cprestore_save_slot_operand"
+ (and (match_code "mem")
+ (match_test "mips_cprestore_address_p (XEXP (op, 0), false)")))
+
+(define_predicate "cprestore_load_slot_operand"
+ (and (match_code "mem")
+ (match_test "mips_cprestore_address_p (XEXP (op, 0), true)")))
+
(define_predicate "consttable_operand"
(match_test "CONSTANT_P (op)"))
diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog
index bcc86bfc148..23e9dbf5b02 100644
--- a/gcc/testsuite/ChangeLog
+++ b/gcc/testsuite/ChangeLog
@@ -1,3 +1,21 @@
+2009-09-14 Richard Sandiford <rdsandiford@googlemail.com>
+
+ * gcc.target/mips/branch-helper.h: New file.
+ * gcc.target/mips/branch-2.c,
+ * gcc.target/mips/branch-3.c,
+ * gcc.target/mips/branch-4.c,
+ * gcc.target/mips/branch-5.c,
+ * gcc.target/mips/branch-6.c,
+ * gcc.target/mips/branch-7.c,
+ * gcc.target/mips/branch-8.c,
+ * gcc.target/mips/branch-9.c,
+ * gcc.target/mips/branch-10.c,
+ * gcc.target/mips/branch-11.c,
+ * gcc.target/mips/branch-12.c,
+ * gcc.target/mips/branch-13.c,
+ * gcc.target/mips/branch-14.c,
+ * gcc.target/mips/branch-15.c: New tests.
+
2009-09-14 Michael Meissner <meissner@linux.vnet.ibm.com>
PR target/41331
diff --git a/gcc/testsuite/gcc.target/mips/branch-10.c b/gcc/testsuite/gcc.target/mips/branch-10.c
new file mode 100644
index 00000000000..7fdebfcc3f6
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/branch-10.c
@@ -0,0 +1,13 @@
+/* { dg-options "-mabicalls -mshared -mabi=n32" } */
+/* { dg-final { scan-assembler-not "(\\\$28|%gp_rel|%got)" } } */
+/* { dg-final { scan-assembler-not "\tjr\t\\\$1\n" } } */
+
+#include "branch-helper.h"
+
+NOMIPS16 void
+foo (void (*bar) (void), volatile int *x)
+{
+ bar ();
+ if (__builtin_expect (*x == 0, 1))
+ OCCUPY_0x1fff8;
+}
diff --git a/gcc/testsuite/gcc.target/mips/branch-11.c b/gcc/testsuite/gcc.target/mips/branch-11.c
new file mode 100644
index 00000000000..1c57f82f533
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/branch-11.c
@@ -0,0 +1,17 @@
+/* { dg-options "-mabicalls -mshared -mabi=n32" } */
+/* { dg-final { scan-assembler "\tsd\t\\\$28," } } */
+/* { dg-final { scan-assembler "\tld\t\\\$28," } } */
+/* { dg-final { scan-assembler "\taddiu\t\\\$28,\\\$28,%lo\\(%neg\\(%gp_rel\\(foo\\)\\)\\)\n" } } */
+/* { dg-final { scan-assembler "\tlw\t\\\$1,%got_page\\(\[^)\]*\\)\\(\\\$28\\)\n" } } */
+/* { dg-final { scan-assembler "\taddiu\t\\\$1,\\\$1,%got_ofst\\(\[^)\]*\\)\n" } } */
+/* { dg-final { scan-assembler "\tjr\t\\\$1\n" } } */
+
+#include "branch-helper.h"
+
+NOMIPS16 void
+foo (void (*bar) (void), volatile int *x)
+{
+ bar ();
+ if (__builtin_expect (*x == 0, 1))
+ OCCUPY_0x1fffc;
+}
diff --git a/gcc/testsuite/gcc.target/mips/branch-12.c b/gcc/testsuite/gcc.target/mips/branch-12.c
new file mode 100644
index 00000000000..f1b6f1e8244
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/branch-12.c
@@ -0,0 +1,13 @@
+/* { dg-options "-mabicalls -mshared -mabi=64" } */
+/* { dg-final { scan-assembler-not "(\\\$28|%gp_rel|%got)" } } */
+/* { dg-final { scan-assembler-not "\tjr\t\\\$1\n" } } */
+
+#include "branch-helper.h"
+
+NOMIPS16 void
+foo (void (*bar) (void), volatile int *x)
+{
+ bar ();
+ if (__builtin_expect (*x == 0, 1))
+ OCCUPY_0x1fff8;
+}
diff --git a/gcc/testsuite/gcc.target/mips/branch-13.c b/gcc/testsuite/gcc.target/mips/branch-13.c
new file mode 100644
index 00000000000..cc0b607d728
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/branch-13.c
@@ -0,0 +1,17 @@
+/* { dg-options "-mabicalls -mshared -mabi=64" } */
+/* { dg-final { scan-assembler "\tsd\t\\\$28," } } */
+/* { dg-final { scan-assembler "\tld\t\\\$28," } } */
+/* { dg-final { scan-assembler "\tdaddiu\t\\\$28,\\\$28,%lo\\(%neg\\(%gp_rel\\(foo\\)\\)\\)\n" } } */
+/* { dg-final { scan-assembler "\tld\t\\\$1,%got_page\\(\[^)\]*\\)\\(\\\$28\\)\n" } } */
+/* { dg-final { scan-assembler "\tdaddiu\t\\\$1,\\\$1,%got_ofst\\(\[^)\]*\\)\n" } } */
+/* { dg-final { scan-assembler "\tjr\t\\\$1\n" } } */
+
+#include "branch-helper.h"
+
+NOMIPS16 void
+foo (void (*bar) (void), volatile int *x)
+{
+ bar ();
+ if (__builtin_expect (*x == 0, 1))
+ OCCUPY_0x1fffc;
+}
diff --git a/gcc/testsuite/gcc.target/mips/branch-14.c b/gcc/testsuite/gcc.target/mips/branch-14.c
new file mode 100644
index 00000000000..026417e162b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/branch-14.c
@@ -0,0 +1,23 @@
+/* An executable version of branch-2.c. */
+/* { dg-do run } */
+
+#include "branch-helper.h"
+
+void __attribute__((noinline))
+foo (volatile int *x)
+{
+ if (__builtin_expect (*x == 0, 1))
+ OCCUPY_0x1fff8;
+}
+
+int
+main (void)
+{
+ int x = 0;
+ int y = 1;
+
+ foo (&x);
+ foo (&y);
+
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/mips/branch-15.c b/gcc/testsuite/gcc.target/mips/branch-15.c
new file mode 100644
index 00000000000..dee7a0504d6
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/branch-15.c
@@ -0,0 +1,23 @@
+/* An executable version of branch-3.c. */
+/* { dg-do run } */
+
+#include "branch-helper.h"
+
+void
+foo (volatile int *x)
+{
+ if (__builtin_expect (*x == 0, 1))
+ OCCUPY_0x1fffc;
+}
+
+int
+main (void)
+{
+ int x = 0;
+ int y = 1;
+
+ foo (&x);
+ foo (&y);
+
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/mips/branch-2.c b/gcc/testsuite/gcc.target/mips/branch-2.c
new file mode 100644
index 00000000000..845e7481729
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/branch-2.c
@@ -0,0 +1,13 @@
+/* { dg-options "-mabicalls -mshared -mabi=32" } */
+/* { dg-final { scan-assembler-not "(\\\$25|\\\$28|cpload)" } } */
+/* { dg-final { scan-assembler-not "\tjr\t\\\$1\n" } } */
+/* { dg-final { scan-assembler-not "cprestore" } } */
+
+#include "branch-helper.h"
+
+NOMIPS16 void
+foo (volatile int *x)
+{
+ if (__builtin_expect (*x == 0, 1))
+ OCCUPY_0x1fff8;
+}
diff --git a/gcc/testsuite/gcc.target/mips/branch-3.c b/gcc/testsuite/gcc.target/mips/branch-3.c
new file mode 100644
index 00000000000..0a4ffbba604
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/branch-3.c
@@ -0,0 +1,13 @@
+/* { dg-options "-mabicalls -mshared -mabi=32" } */
+/* { dg-final { scan-assembler "\t\\.cpload\t\\\$25\n" } } */
+/* { dg-final { scan-assembler "\tjr\t\\\$1\n" } } */
+/* { dg-final { scan-assembler-not "cprestore" } } */
+
+#include "branch-helper.h"
+
+NOMIPS16 void
+foo (volatile int *x)
+{
+ if (__builtin_expect (*x == 0, 1))
+ OCCUPY_0x1fffc;
+}
diff --git a/gcc/testsuite/gcc.target/mips/branch-4.c b/gcc/testsuite/gcc.target/mips/branch-4.c
new file mode 100644
index 00000000000..277bd0af76f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/branch-4.c
@@ -0,0 +1,12 @@
+/* { dg-options "-mabicalls -mshared -mabi=n32" } */
+/* { dg-final { scan-assembler-not "(\\\$25|\\\$28|%gp_rel|%got)" } } */
+/* { dg-final { scan-assembler-not "\tjr\t\\\$1\n" } } */
+
+#include "branch-helper.h"
+
+NOMIPS16 void
+foo (volatile int *x)
+{
+ if (__builtin_expect (*x == 0, 1))
+ OCCUPY_0x1fff8;
+}
diff --git a/gcc/testsuite/gcc.target/mips/branch-5.c b/gcc/testsuite/gcc.target/mips/branch-5.c
new file mode 100644
index 00000000000..3d151d824ef
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/branch-5.c
@@ -0,0 +1,14 @@
+/* { dg-options "-mabicalls -mshared -mabi=n32" } */
+/* { dg-final { scan-assembler "\taddiu\t\\\$3,\\\$3,%lo\\(%neg\\(%gp_rel\\(foo\\)\\)\\)\n" } } */
+/* { dg-final { scan-assembler "\tlw\t\\\$1,%got_page\\(\[^)\]*\\)\\(\\\$3\\)\\n" } } */
+/* { dg-final { scan-assembler "\tjr\t\\\$1\n" } } */
+/* { dg-final { scan-assembler-not "\\\$28" } } */
+
+#include "branch-helper.h"
+
+NOMIPS16 void
+foo (volatile int *x)
+{
+ if (__builtin_expect (*x == 0, 1))
+ OCCUPY_0x1fffc;
+}
diff --git a/gcc/testsuite/gcc.target/mips/branch-6.c b/gcc/testsuite/gcc.target/mips/branch-6.c
new file mode 100644
index 00000000000..9bf73f01c9b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/branch-6.c
@@ -0,0 +1,12 @@
+/* { dg-options "-mabicalls -mshared -mabi=64" } */
+/* { dg-final { scan-assembler-not "(\\\$25|\\\$28|%gp_rel|%got)" } } */
+/* { dg-final { scan-assembler-not "\tjr\t\\\$1\n" } } */
+
+#include "branch-helper.h"
+
+NOMIPS16 void
+foo (volatile int *x)
+{
+ if (__builtin_expect (*x == 0, 1))
+ OCCUPY_0x1fff8;
+}
diff --git a/gcc/testsuite/gcc.target/mips/branch-7.c b/gcc/testsuite/gcc.target/mips/branch-7.c
new file mode 100644
index 00000000000..053ec610c3d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/branch-7.c
@@ -0,0 +1,14 @@
+/* { dg-options "-mabicalls -mshared -mabi=64" } */
+/* { dg-final { scan-assembler "\tdaddiu\t\\\$3,\\\$3,%lo\\(%neg\\(%gp_rel\\(foo\\)\\)\\)\n" } } */
+/* { dg-final { scan-assembler "\tld\t\\\$1,%got_page\\(\[^)\]*\\)\\(\\\$3\\)\\n" } } */
+/* { dg-final { scan-assembler "\tjr\t\\\$1\n" } } */
+/* { dg-final { scan-assembler-not "\\\$28" } } */
+
+#include "branch-helper.h"
+
+NOMIPS16 void
+foo (volatile int *x)
+{
+ if (__builtin_expect (*x == 0, 1))
+ OCCUPY_0x1fffc;
+}
diff --git a/gcc/testsuite/gcc.target/mips/branch-8.c b/gcc/testsuite/gcc.target/mips/branch-8.c
new file mode 100644
index 00000000000..c2cbae36905
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/branch-8.c
@@ -0,0 +1,13 @@
+/* { dg-options "-mabicalls -mshared -mabi=32" } */
+/* { dg-final { scan-assembler-not "(\\\$28|cpload|cprestore)" } } */
+/* { dg-final { scan-assembler-not "\tjr\t\\\$1\n" } } */
+
+#include "branch-helper.h"
+
+NOMIPS16 void
+foo (void (*bar) (void), volatile int *x)
+{
+ bar ();
+ if (__builtin_expect (*x == 0, 1))
+ OCCUPY_0x1fff8;
+}
diff --git a/gcc/testsuite/gcc.target/mips/branch-9.c b/gcc/testsuite/gcc.target/mips/branch-9.c
new file mode 100644
index 00000000000..2b83ea5b591
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/branch-9.c
@@ -0,0 +1,18 @@
+/* { dg-options "-mabicalls -mshared -mabi=32" } */
+/* { dg-final { scan-assembler "\t\\.cpload\t\\\$25\n" } } */
+/* { dg-final { scan-assembler "\t\\.cprestore\t16\n" } } */
+/* { dg-final { scan-assembler "\tlw\t\\\$1,16\\(\\\$fp\\)\n" } } */
+/* { dg-final { scan-assembler "\tlw\t\\\$1,%got\\(\[^)\]*\\)\\(\\\$1\\)\n" } } */
+/* { dg-final { scan-assembler "\taddiu\t\\\$1,\\\$1,%lo\\(\[^)\]*\\)\n" } } */
+/* { dg-final { scan-assembler "\tjr\t\\\$1\n" } } */
+/* { dg-final { scan-assembler-not "\tlw\t\\\$28,16\\(\\\$sp\\)\n" } } */
+
+#include "branch-helper.h"
+
+NOMIPS16 void
+foo (void (*bar) (void), volatile int *x)
+{
+ bar ();
+ if (__builtin_expect (*x == 0, 1))
+ OCCUPY_0x1fffc;
+}
diff --git a/gcc/testsuite/gcc.target/mips/branch-helper.h b/gcc/testsuite/gcc.target/mips/branch-helper.h
new file mode 100644
index 00000000000..85399be4c7d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/branch-helper.h
@@ -0,0 +1,37 @@
+/* DN(X) generates 2**N copies of asm instruction X. */
+#define D0(X) X
+#define D1(X) X "\n\t" X
+#define D2(X) D1 (D1 (X))
+#define D3(X) D2 (D1 (X))
+#define D4(X) D2 (D2 (X))
+#define D5(X) D4 (D1 (X))
+#define D6(X) D4 (D2 (X))
+#define D7(X) D4 (D2 (D1 (X)))
+#define D8(X) D4 (D4 (X))
+#define D9(X) D8 (D1 (X))
+#define D10(X) D8 (D2 (X))
+#define D11(X) D8 (D2 (D1 (X)))
+#define D12(X) D8 (D4 (X))
+#define D13(X) D8 (D4 (D1 (X)))
+#define D14(X) D8 (D4 (D2 (X)))
+
+/* Emit something that is 0x1fff8 bytes long, which is the largest
+ permissible range for non-MIPS16 forward branches. */
+#define OCCUPY_0x1fff8 \
+ asm (D14 ("nop") "\n\t" \
+ D13 ("nop") "\n\t" \
+ D12 ("nop") "\n\t" \
+ D11 ("nop") "\n\t" \
+ D10 ("nop") "\n\t" \
+ D9 ("nop") "\n\t" \
+ D8 ("nop") "\n\t" \
+ D7 ("nop") "\n\t" \
+ D6 ("nop") "\n\t" \
+ D5 ("nop") "\n\t" \
+ D4 ("nop") "\n\t" \
+ D3 ("nop") "\n\t" \
+ D2 ("nop") "\n\t" \
+ D1 ("nop"))
+
+/* Likewise emit something that is 0x1fffc bytes long. */
+#define OCCUPY_0x1fffc do { asm ("nop"); OCCUPY_0x1fff8; } while (0)