+static bfd_boolean
+has_label_name (const char *arr[], size_t len ,const char *s)
+{
+ unsigned long i;
+ for (i = 0; i < len; i++)
+ {
+ if (!arr[i])
+ return FALSE;
+ if (streq (arr[i], s))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* Fix loongson3 llsc errata: Insert sync before ll/lld. */
+
+static void
+fix_loongson3_llsc (struct mips_cl_insn * ip)
+{
+ gas_assert (!HAVE_CODE_COMPRESSION);
+
+ /* If is an local label and the insn is not sync,
+ look forward that whether an branch between ll/sc jump to here
+ if so, insert a sync. */
+ if (seg_info (now_seg)->label_list
+ && S_IS_LOCAL (seg_info (now_seg)->label_list->label)
+ && (strcmp (ip->insn_mo->name, "sync") != 0))
+ {
+ unsigned long i;
+ valueT label_value;
+ const char *label_names[MAX_LABELS_SAME];
+ const char *label_name;
+
+ label_name = S_GET_NAME (seg_info (now_seg)->label_list->label);
+ label_names[0] = label_name;
+ struct insn_label_list *llist = seg_info (now_seg)->label_list;
+ label_value = S_GET_VALUE (llist->label);
+
+ for (i = 1; i < MAX_LABELS_SAME; i++)
+ {
+ llist = llist->next;
+ if (!llist)
+ break;
+ if (S_GET_VALUE (llist->label) == label_value)
+ label_names[i] = S_GET_NAME (llist->label);
+ else
+ break;
+ }
+ for (; i < MAX_LABELS_SAME; i++)
+ label_names[i] = NULL;
+
+ unsigned long lookback = ARRAY_SIZE (history);
+ for (i = 0; i < lookback; i++)
+ {
+ if (streq (history[i].insn_mo->name, "ll")
+ || streq (history[i].insn_mo->name, "lld"))
+ break;
+
+ if (streq (history[i].insn_mo->name, "sc")
+ || streq (history[i].insn_mo->name, "scd"))
+ {
+ unsigned long j;
+
+ for (j = i + 1; j < lookback; j++)
+ {
+ if (streq (history[i].insn_mo->name, "ll")
+ || streq (history[i].insn_mo->name, "lld"))
+ break;
+
+ if (delayed_branch_p (&history[j]))
+ {
+ if (has_label_name (label_names,
+ MAX_LABELS_SAME,
+ history[j].target))
+ {
+ add_fixed_insn (&sync_insn);
+ insert_into_history (0, 1, &sync_insn);
+ i = lookback;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ /* If we find a sc, we look forward to look for an branch insn,
+ and see whether it jump back and out of ll/sc. */
+ else if (streq (ip->insn_mo->name, "sc") || streq (ip->insn_mo->name, "scd"))
+ {
+ unsigned long lookback = ARRAY_SIZE (history) - 1;
+ unsigned long i;
+
+ for (i = 0; i < lookback; i++)
+ {
+ if (streq (history[i].insn_mo->name, "ll")
+ || streq (history[i].insn_mo->name, "lld"))
+ break;
+
+ if (delayed_branch_p (&history[i]))
+ {
+ unsigned long j;
+
+ for (j = i + 1; j < lookback; j++)
+ {
+ if (streq (history[j].insn_mo->name, "ll")
+ || streq (history[i].insn_mo->name, "lld"))
+ break;
+ }
+
+ for (; j < lookback; j++)
+ {
+ if (history[j].label[0] != '\0'
+ && streq (history[j].label, history[i].target)
+ && strcmp (history[j+1].insn_mo->name, "sync") != 0)
+ {
+ add_fixed_insn (&sync_insn);
+ insert_into_history (++j, 1, &sync_insn);
+ }
+ }
+ }
+ }
+ }
+
+ /* Skip if there is a sync before ll/lld. */
+ if ((strcmp (ip->insn_mo->name, "ll") == 0
+ || strcmp (ip->insn_mo->name, "lld") == 0)
+ && (strcmp (history[0].insn_mo->name, "sync") != 0))
+ {
+ add_fixed_insn (&sync_insn);
+ insert_into_history (0, 1, &sync_insn);
+ }
+}
+