===================================================================
@@ -123,6 +123,12 @@
(SUBINSN) != NEXT_INSN (SEQ_END (INSN)); \
(SUBINSN) = NEXT_INSN (SUBINSN))
+/* Walk the instruction chain either backwards or forwards. */
+#define WALK_INSNS(START_INSN, INSN, WALK_FUNCTION) \
+ for ((INSN) = (WALK_FUNCTION) (START_INSN); \
+ (INSN) != 0; \
+ (INSN) = (WALK_FUNCTION) (INSN))
+
/* True if bit BIT is set in VALUE. */
#define BITSET_P(VALUE, BIT) (((VALUE) & (1 << (BIT))) != 0)
@@ -14938,7 +14944,214 @@
}
return INTVAL (offset) <= entry->offset;
}
+#define OK_FOR_MIPS16_BRANCH(OFFSET) ((OFFSET) >= -65000 && (OFFSET) <= 65000)
+static int
+mips16_count_matching_branches (rtx jump_insn)
+{
+ rtx insn;
+ int label_uid, jump_uid;
+ int matches = 0;
+
+ jump_uid = INSN_UID (jump_insn);
+ label_uid = INSN_UID (JUMP_LABEL (jump_insn));
+
+ /* Count the number of branches to the same label. */
+ for (insn = get_insns (); insn != 0; insn = NEXT_INSN (insn))
+ {
+ if (simplejump_p (insn))
+ {
+ int insn_uid = INSN_UID (JUMP_LABEL (insn));
+ if (insn_uid != jump_uid && label_uid == insn_uid)
+ matches++;
+ }
+ }
+
+ return matches;
+}
+
+static rtx
+walk_insns_forward (rtx insn)
+{
+ return NEXT_INSN (insn);
+}
+
+static rtx
+walk_insns_reverse (rtx insn)
+{
+ return PREV_INSN (insn);
+}
+
+static void
+mips16_update_branch_info (mips16_branch_info_t *previous,
+ mips16_branch_info_t *current,
+ int insn_uid)
+{
+ /* Move current to previous. */
+ previous->uid = current->uid;
+ previous->address = current->address;
+ previous->distance_to_dest = current->distance_to_dest;
+ previous->distance_from_src = current->distance_from_src;
+
+ /* Update current. */
+ current->uid = insn_uid;
+ current->address = INSN_ADDRESSES (insn_uid);
+}
+
+static void
+mips16_zero_branch_info (mips16_branch_info_t *branch_info)
+{
+ branch_info->uid = 0;
+ branch_info->address = 0;
+ branch_info->distance_to_dest = 0;
+ branch_info->distance_from_src = 0;
+}
+
+static void
+mips16_check_branches (rtx jump_insn)
+{
+ int label1_uid, label2_uid, jump_uid, insn_uid;
+ int offset;
+ rtx label1;
+ rtx insn, new_label, new_label_insn, new_jump, new_jump_label;
+
+ rtx (*walk_function) (rtx);
+
+ mips16_branch_info_t first_branch, current, previous;
+
+ mips16_zero_branch_info (&first_branch);
+ mips16_zero_branch_info (¤t);
+ mips16_zero_branch_info (&previous);
+
+ jump_uid = INSN_UID (jump_insn);
+ first_branch.uid = jump_uid;
+ first_branch.address = INSN_ADDRESSES (jump_uid);
+
+ /* Don't reprocess branches that were introduced by mips16_check_branches.
+ The branches are known to be within range. We'll update INSN_ADDRESSES
+ for all of the new branches when we finish here. */
+ if (first_branch.address == -1)
+ return;
+
+ label1 = JUMP_LABEL (jump_insn);
+ label1_uid = INSN_UID (label1);
+
+ offset = INSN_ADDRESSES (label1_uid) - INSN_ADDRESSES (jump_uid);
+ first_branch.distance_to_dest = offset;
+
+ /* The branch offset is in range, nothing to do. */
+ if (OK_FOR_MIPS16_BRANCH (offset))
+ return;
+
+ if (offset > 0)
+ walk_function = &walk_insns_forward;
+ else
+ walk_function = &walk_insns_reverse;
+
+ /* The branch offset is too large. First figure out if there are
+ other branches to the same destination. If other branches to
+ the same destination are available, we'll branch there instead. */
+
+ if (mips16_count_matching_branches (jump_insn))
+ {
+ /* If branches to the same label are available and in range,
+ then use those first. */
+ WALK_INSNS (jump_insn, insn, walk_function)
+ {
+ if (JUMP_P (insn))
+ {
+ insn_uid = INSN_UID (insn);
+ label2_uid = INSN_UID (JUMP_LABEL (insn));
+
+ if (label2_uid != label1_uid)
+ continue;
+
+ mips16_update_branch_info (&previous, ¤t, insn_uid);
+ offset = INSN_ADDRESSES (insn_uid) - INSN_ADDRESSES (jump_uid);
+ current.distance_from_src = offset;
+ if (!OK_FOR_MIPS16_BRANCH (offset))
+ {
+ /* If the previous candidate was in range, use it. */
+ if (previous.address != 0
+ && OK_FOR_MIPS16_BRANCH (previous.distance_from_src))
+ {
+ new_label = gen_label_rtx ();
+ emit_label_before (new_label, insn);
+ redirect_jump (jump_insn, new_label, 0);
+ return;
+ }
+ continue;
+ }
+
+ offset = INSN_ADDRESSES (label2_uid) - INSN_ADDRESSES (insn_uid);
+ current.distance_to_dest = offset;
+ if (!OK_FOR_MIPS16_BRANCH (offset))
+ continue;
+
+ /* The offset is in range. The new destination will
+ be this branch. */
+ new_label = gen_label_rtx ();
+ emit_label_before (new_label, insn);
+ redirect_jump (jump_insn, new_label, 0);
+ return;
+ }
+ }
+ }
+
+ /* Reset current and previous branch information. */
+ mips16_zero_branch_info (¤t);
+ mips16_zero_branch_info (&previous);
+
+ /* Search for a barrier instruction/insert a label and a jump. */
+ WALK_INSNS (jump_insn, insn, walk_function)
+ {
+ int barrier_off, label_off;
+
+ if (GET_CODE (insn) == BARRIER)
+ {
+ insn_uid = INSN_UID (insn);
+ mips16_update_branch_info (&previous, ¤t, insn_uid);
+
+ barrier_off = INSN_ADDRESSES (insn_uid) - INSN_ADDRESSES (jump_uid);
+ label_off = INSN_ADDRESSES (label1_uid) - INSN_ADDRESSES (insn_uid);
+ current.distance_from_src = barrier_off;
+ current.distance_to_dest = label_off;
+
+ if (!OK_FOR_MIPS16_BRANCH (barrier_off))
+ {
+ /* If the previous candidate was in range, use that instead. */
+ if (previous.address != 0
+ && OK_FOR_MIPS16_BRANCH (previous.distance_from_src))
+ {
+ new_label = gen_label_rtx ();
+ new_label_insn = emit_label_before (new_label, insn);
+ redirect_jump (jump_insn, new_label, 0);
+ new_jump_label = gen_jump (label1);
+ new_jump = emit_jump_insn_after (new_jump_label,
+ new_label_insn);
+ INSN_ADDRESSES_NEW (new_jump, -1);
+ JUMP_LABEL (new_jump) = new_jump_label;
+ return;
+ }
+ continue;
+ }
+
+ /* If the offset is too large, search for another candidate. */
+ if (!OK_FOR_MIPS16_BRANCH (label_off))
+ continue;
+
+ new_label = gen_label_rtx ();
+ new_label_insn = emit_label_before (new_label, insn);
+ redirect_jump (jump_insn, new_label, 0);
+ new_jump_label = gen_jump (label1);
+ new_jump = emit_jump_insn_after (new_jump_label, new_label_insn);
+ INSN_ADDRESSES_NEW (new_jump, -1);
+ JUMP_LABEL (new_jump) = new_jump_label;
+ return;
+ }
+ }
+}
+
/* A for_each_rtx callback for which DATA is a mips_lo_sum_offset hash table.
Record every LO_SUM in *LOC. */
@@ -15103,6 +15316,17 @@
htab = htab_create (37, mips_lo_sum_offset_hash,
mips_lo_sum_offset_eq, free);
+ /* Ensure that MIPS16 branches are within range. */
+ if (TARGET_MIPS16)
+ {
+ for (insn = get_insns (); insn != 0; insn = NEXT_INSN (insn))
+ {
+ if (simplejump_p (insn) || any_condjump_p (insn))
+ mips16_check_branches (insn);
+ }
+ shorten_branches (get_insns ());
+ }
+
/* Make a first pass over the instructions, recording all the LO_SUMs. */
for (insn = get_insns (); insn != 0; insn = NEXT_INSN (insn))
FOR_EACH_SUBINSN (subinsn, insn)