+}
+
+/* If C is a prefix class, then return the EXT string without the prefix.
+ Otherwise return the entire EXT string. */
+
+static const char *
+riscv_skip_prefix (const char *ext, riscv_isa_ext_class_t c)
+{
+ switch (c)
+ {
+ case RV_ISA_CLASS_X: return &ext[1];
+ case RV_ISA_CLASS_S: return &ext[1];
+ case RV_ISA_CLASS_Z: return &ext[1];
+ default: return ext;
+ }
+}
+
+/* Compare prefixed extension names canonically. */
+
+static int
+riscv_prefix_cmp (const char *a, const char *b)
+{
+ riscv_isa_ext_class_t ca = riscv_get_prefix_class (a);
+ riscv_isa_ext_class_t cb = riscv_get_prefix_class (b);
+
+ /* Extension name without prefix */
+ const char *anp = riscv_skip_prefix (a, ca);
+ const char *bnp = riscv_skip_prefix (b, cb);
+
+ if (ca == cb)
+ return strcasecmp (anp, bnp);
+
+ return (int)ca - (int)cb;
+}
+
+/* Merge multi letter extensions. PIN is a pointer to the head of the input
+ object subset list. Likewise for POUT and the output object. Return TRUE
+ on success and FALSE when a conflict is found. */
+
+static bfd_boolean
+riscv_merge_multi_letter_ext (bfd *ibfd,
+ riscv_subset_t **pin,
+ riscv_subset_t **pout)
+{
+ riscv_subset_t *in = *pin;
+ riscv_subset_t *out = *pout;
+ riscv_subset_t *tail;
+
+ int cmp;
+
+ while (in && out)
+ {
+ cmp = riscv_prefix_cmp (in->name, out->name);
+
+ if (cmp < 0)
+ {
+ /* `in' comes before `out', append `in' and increment. */
+ riscv_add_subset (&merged_subsets, in->name, in->major_version,
+ in->minor_version);
+ in = in->next;
+ }
+ else if (cmp > 0)
+ {
+ /* `out' comes before `in', append `out' and increment. */
+ riscv_add_subset (&merged_subsets, out->name, out->major_version,
+ out->minor_version);
+ out = out->next;
+ }
+ else
+ {
+ /* Both present, check version and increment both. */
+ if ((in->major_version != out->major_version)
+ || (in->minor_version != out->minor_version))
+ {
+ riscv_version_mismatch (ibfd, in, out);
+ return FALSE;
+ }
+
+ riscv_add_subset (&merged_subsets, out->name, out->major_version,
+ out->minor_version);
+ out = out->next;
+ in = in->next;
+ }
+ }
+
+ if (in || out) {
+ /* If we're here, either `in' or `out' is running longer than
+ the other. So, we need to append the corresponding tail. */
+ tail = in ? in : out;
+
+ while (tail)
+ {
+ riscv_add_subset (&merged_subsets, tail->name, tail->major_version,
+ tail->minor_version);
+ tail = tail->next;
+ }
+ }
+
+ return TRUE;
+}
+
+/* Merge Tag_RISCV_arch attribute. */
+
+static char *
+riscv_merge_arch_attr_info (bfd *ibfd, char *in_arch, char *out_arch)
+{
+ riscv_subset_t *in, *out;
+ char *merged_arch_str;
+
+ unsigned xlen_in, xlen_out;
+ merged_subsets.head = NULL;
+ merged_subsets.tail = NULL;
+
+ riscv_parse_subset_t rpe_in;
+ riscv_parse_subset_t rpe_out;
+
+ /* Only assembler needs to check the default version of ISA, so just set
+ the rpe_in.get_default_version and rpe_out.get_default_version to NULL. */
+ rpe_in.subset_list = &in_subsets;
+ rpe_in.error_handler = _bfd_error_handler;
+ rpe_in.xlen = &xlen_in;
+ rpe_in.get_default_version = NULL;
+
+ rpe_out.subset_list = &out_subsets;
+ rpe_out.error_handler = _bfd_error_handler;
+ rpe_out.xlen = &xlen_out;
+ rpe_out.get_default_version = NULL;
+
+ if (in_arch == NULL && out_arch == NULL)
+ return NULL;
+
+ if (in_arch == NULL && out_arch != NULL)
+ return out_arch;
+
+ if (in_arch != NULL && out_arch == NULL)
+ return in_arch;
+
+ /* Parse subset from arch string. */
+ if (!riscv_parse_subset (&rpe_in, in_arch))
+ return NULL;
+
+ if (!riscv_parse_subset (&rpe_out, out_arch))
+ return NULL;
+
+ /* Checking XLEN. */
+ if (xlen_out != xlen_in)
+ {
+ _bfd_error_handler
+ (_("error: %pB: ISA string of input (%s) doesn't match "
+ "output (%s)."), ibfd, in_arch, out_arch);
+ return NULL;
+ }
+
+ /* Merge subset list. */
+ in = in_subsets.head;
+ out = out_subsets.head;
+
+ /* Merge standard extension. */
+ if (!riscv_merge_std_ext (ibfd, in_arch, out_arch, &in, &out))
+ return NULL;
+
+ /* Merge all non-single letter extensions with single call. */
+ if (!riscv_merge_multi_letter_ext (ibfd, &in, &out))
+ return NULL;
+
+ if (xlen_in != xlen_out)
+ {
+ _bfd_error_handler
+ (_("error: %pB: XLEN of input (%u) doesn't match "
+ "output (%u)."), ibfd, xlen_in, xlen_out);
+ return NULL;
+ }
+
+ if (xlen_in != ARCH_SIZE)
+ {
+ _bfd_error_handler
+ (_("error: %pB: Unsupported XLEN (%u), you might be "
+ "using wrong emulation."), ibfd, xlen_in);
+ return NULL;
+ }
+
+ merged_arch_str = riscv_arch_str (ARCH_SIZE, &merged_subsets);
+
+ /* Release the subset lists. */
+ riscv_release_subset_list (&in_subsets);
+ riscv_release_subset_list (&out_subsets);
+ riscv_release_subset_list (&merged_subsets);
+
+ return merged_arch_str;
+}
+
+/* Merge object attributes from IBFD into output_bfd of INFO.
+ Raise an error if there are conflicting attributes. */
+
+static bfd_boolean
+riscv_merge_attributes (bfd *ibfd, struct bfd_link_info *info)
+{
+ bfd *obfd = info->output_bfd;
+ obj_attribute *in_attr;
+ obj_attribute *out_attr;
+ bfd_boolean result = TRUE;
+ bfd_boolean priv_may_conflict = FALSE;
+ bfd_boolean in_priv_zero = TRUE;
+ bfd_boolean out_priv_zero = TRUE;
+ const char *sec_name = get_elf_backend_data (ibfd)->obj_attrs_section;
+ unsigned int i;
+
+ /* Skip linker created files. */
+ if (ibfd->flags & BFD_LINKER_CREATED)
+ return TRUE;
+
+ /* Skip any input that doesn't have an attribute section.
+ This enables to link object files without attribute section with
+ any others. */
+ if (bfd_get_section_by_name (ibfd, sec_name) == NULL)
+ return TRUE;
+
+ if (!elf_known_obj_attributes_proc (obfd)[0].i)
+ {
+ /* This is the first object. Copy the attributes. */
+ _bfd_elf_copy_obj_attributes (ibfd, obfd);
+
+ out_attr = elf_known_obj_attributes_proc (obfd);
+
+ /* Use the Tag_null value to indicate the attributes have been
+ initialized. */
+ out_attr[0].i = 1;
+
+ return TRUE;
+ }