+// Return the MACH for each .MIPS.abiflags ISA Extension.
+
+template<int size, bool big_endian>
+unsigned int
+Target_mips<size, big_endian>::mips_isa_ext_mach(unsigned int isa_ext)
+{
+ switch (isa_ext)
+ {
+ case elfcpp::AFL_EXT_3900:
+ return mach_mips3900;
+
+ case elfcpp::AFL_EXT_4010:
+ return mach_mips4010;
+
+ case elfcpp::AFL_EXT_4100:
+ return mach_mips4100;
+
+ case elfcpp::AFL_EXT_4111:
+ return mach_mips4111;
+
+ case elfcpp::AFL_EXT_4120:
+ return mach_mips4120;
+
+ case elfcpp::AFL_EXT_4650:
+ return mach_mips4650;
+
+ case elfcpp::AFL_EXT_5400:
+ return mach_mips5400;
+
+ case elfcpp::AFL_EXT_5500:
+ return mach_mips5500;
+
+ case elfcpp::AFL_EXT_5900:
+ return mach_mips5900;
+
+ case elfcpp::AFL_EXT_10000:
+ return mach_mips10000;
+
+ case elfcpp::AFL_EXT_LOONGSON_2E:
+ return mach_mips_loongson_2e;
+
+ case elfcpp::AFL_EXT_LOONGSON_2F:
+ return mach_mips_loongson_2f;
+
+ case elfcpp::AFL_EXT_LOONGSON_3A:
+ return mach_mips_loongson_3a;
+
+ case elfcpp::AFL_EXT_SB1:
+ return mach_mips_sb1;
+
+ case elfcpp::AFL_EXT_OCTEON:
+ return mach_mips_octeon;
+
+ case elfcpp::AFL_EXT_OCTEONP:
+ return mach_mips_octeonp;
+
+ case elfcpp::AFL_EXT_OCTEON2:
+ return mach_mips_octeon2;
+
+ case elfcpp::AFL_EXT_XLR:
+ return mach_mips_xlr;
+
+ default:
+ return mach_mips3000;
+ }
+}
+
+// Return the .MIPS.abiflags value representing each ISA Extension.
+
+template<int size, bool big_endian>
+unsigned int
+Target_mips<size, big_endian>::mips_isa_ext(unsigned int mips_mach)
+{
+ switch (mips_mach)
+ {
+ case mach_mips3900:
+ return elfcpp::AFL_EXT_3900;
+
+ case mach_mips4010:
+ return elfcpp::AFL_EXT_4010;
+
+ case mach_mips4100:
+ return elfcpp::AFL_EXT_4100;
+
+ case mach_mips4111:
+ return elfcpp::AFL_EXT_4111;
+
+ case mach_mips4120:
+ return elfcpp::AFL_EXT_4120;
+
+ case mach_mips4650:
+ return elfcpp::AFL_EXT_4650;
+
+ case mach_mips5400:
+ return elfcpp::AFL_EXT_5400;
+
+ case mach_mips5500:
+ return elfcpp::AFL_EXT_5500;
+
+ case mach_mips5900:
+ return elfcpp::AFL_EXT_5900;
+
+ case mach_mips10000:
+ return elfcpp::AFL_EXT_10000;
+
+ case mach_mips_loongson_2e:
+ return elfcpp::AFL_EXT_LOONGSON_2E;
+
+ case mach_mips_loongson_2f:
+ return elfcpp::AFL_EXT_LOONGSON_2F;
+
+ case mach_mips_loongson_3a:
+ return elfcpp::AFL_EXT_LOONGSON_3A;
+
+ case mach_mips_sb1:
+ return elfcpp::AFL_EXT_SB1;
+
+ case mach_mips_octeon:
+ return elfcpp::AFL_EXT_OCTEON;
+
+ case mach_mips_octeonp:
+ return elfcpp::AFL_EXT_OCTEONP;
+
+ case mach_mips_octeon3:
+ return elfcpp::AFL_EXT_OCTEON3;
+
+ case mach_mips_octeon2:
+ return elfcpp::AFL_EXT_OCTEON2;
+
+ case mach_mips_xlr:
+ return elfcpp::AFL_EXT_XLR;
+
+ default:
+ return 0;
+ }
+}
+
+// Update the isa_level, isa_rev, isa_ext fields of abiflags.
+
+template<int size, bool big_endian>
+void
+Target_mips<size, big_endian>::update_abiflags_isa(const std::string& name,
+ elfcpp::Elf_Word e_flags, Mips_abiflags<big_endian>* abiflags)
+{
+ int new_isa = 0;
+ switch (e_flags & elfcpp::EF_MIPS_ARCH)
+ {
+ case elfcpp::E_MIPS_ARCH_1:
+ new_isa = this->level_rev(1, 0);
+ break;
+ case elfcpp::E_MIPS_ARCH_2:
+ new_isa = this->level_rev(2, 0);
+ break;
+ case elfcpp::E_MIPS_ARCH_3:
+ new_isa = this->level_rev(3, 0);
+ break;
+ case elfcpp::E_MIPS_ARCH_4:
+ new_isa = this->level_rev(4, 0);
+ break;
+ case elfcpp::E_MIPS_ARCH_5:
+ new_isa = this->level_rev(5, 0);
+ break;
+ case elfcpp::E_MIPS_ARCH_32:
+ new_isa = this->level_rev(32, 1);
+ break;
+ case elfcpp::E_MIPS_ARCH_32R2:
+ new_isa = this->level_rev(32, 2);
+ break;
+ case elfcpp::E_MIPS_ARCH_32R6:
+ new_isa = this->level_rev(32, 6);
+ break;
+ case elfcpp::E_MIPS_ARCH_64:
+ new_isa = this->level_rev(64, 1);
+ break;
+ case elfcpp::E_MIPS_ARCH_64R2:
+ new_isa = this->level_rev(64, 2);
+ break;
+ case elfcpp::E_MIPS_ARCH_64R6:
+ new_isa = this->level_rev(64, 6);
+ break;
+ default:
+ gold_error(_("%s: Unknown architecture %s"), name.c_str(),
+ this->elf_mips_mach_name(e_flags));
+ }
+
+ if (new_isa > this->level_rev(abiflags->isa_level, abiflags->isa_rev))
+ {
+ // Decode a single value into level and revision.
+ abiflags->isa_level = new_isa >> 3;
+ abiflags->isa_rev = new_isa & 0x7;
+ }
+
+ // Update the isa_ext if needed.
+ if (this->mips_mach_extends(this->mips_isa_ext_mach(abiflags->isa_ext),
+ this->elf_mips_mach(e_flags)))
+ abiflags->isa_ext = this->mips_isa_ext(this->elf_mips_mach(e_flags));
+}
+
+// Infer the content of the ABI flags based on the elf header.
+
+template<int size, bool big_endian>
+void
+Target_mips<size, big_endian>::infer_abiflags(
+ Mips_relobj<size, big_endian>* relobj, Mips_abiflags<big_endian>* abiflags)
+{
+ const Attributes_section_data* pasd = relobj->attributes_section_data();
+ int attr_fp_abi = elfcpp::Val_GNU_MIPS_ABI_FP_ANY;
+ elfcpp::Elf_Word e_flags = relobj->processor_specific_flags();
+
+ this->update_abiflags_isa(relobj->name(), e_flags, abiflags);
+ if (pasd != NULL)
+ {
+ // Read fp_abi from the .gnu.attribute section.
+ const Object_attribute* attr =
+ pasd->known_attributes(Object_attribute::OBJ_ATTR_GNU);
+ attr_fp_abi = attr[elfcpp::Tag_GNU_MIPS_ABI_FP].int_value();
+ }
+
+ abiflags->fp_abi = attr_fp_abi;
+ abiflags->cpr1_size = elfcpp::AFL_REG_NONE;
+ abiflags->cpr2_size = elfcpp::AFL_REG_NONE;
+ abiflags->gpr_size = this->mips_32bit_flags(e_flags) ? elfcpp::AFL_REG_32
+ : elfcpp::AFL_REG_64;
+
+ if (abiflags->fp_abi == elfcpp::Val_GNU_MIPS_ABI_FP_SINGLE
+ || abiflags->fp_abi == elfcpp::Val_GNU_MIPS_ABI_FP_XX
+ || (abiflags->fp_abi == elfcpp::Val_GNU_MIPS_ABI_FP_DOUBLE
+ && abiflags->gpr_size == elfcpp::AFL_REG_32))
+ abiflags->cpr1_size = elfcpp::AFL_REG_32;
+ else if (abiflags->fp_abi == elfcpp::Val_GNU_MIPS_ABI_FP_DOUBLE
+ || abiflags->fp_abi == elfcpp::Val_GNU_MIPS_ABI_FP_64
+ || abiflags->fp_abi == elfcpp::Val_GNU_MIPS_ABI_FP_64A)
+ abiflags->cpr1_size = elfcpp::AFL_REG_64;
+
+ if (e_flags & elfcpp::EF_MIPS_ARCH_ASE_MDMX)
+ abiflags->ases |= elfcpp::AFL_ASE_MDMX;
+ if (e_flags & elfcpp::EF_MIPS_ARCH_ASE_M16)
+ abiflags->ases |= elfcpp::AFL_ASE_MIPS16;
+ if (e_flags & elfcpp::EF_MIPS_ARCH_ASE_MICROMIPS)
+ abiflags->ases |= elfcpp::AFL_ASE_MICROMIPS;
+
+ if (abiflags->fp_abi != elfcpp::Val_GNU_MIPS_ABI_FP_ANY
+ && abiflags->fp_abi != elfcpp::Val_GNU_MIPS_ABI_FP_SOFT
+ && abiflags->fp_abi != elfcpp::Val_GNU_MIPS_ABI_FP_64A
+ && abiflags->isa_level >= 32
+ && abiflags->isa_ext != elfcpp::AFL_EXT_LOONGSON_3A)
+ abiflags->flags1 |= elfcpp::AFL_FLAGS1_ODDSPREG;
+}
+
+// Create abiflags from elf header or from .MIPS.abiflags section.
+
+template<int size, bool big_endian>
+void
+Target_mips<size, big_endian>::create_abiflags(
+ Mips_relobj<size, big_endian>* relobj,
+ Mips_abiflags<big_endian>* abiflags)
+{
+ Mips_abiflags<big_endian>* sec_abiflags = relobj->abiflags();
+ Mips_abiflags<big_endian> header_abiflags;
+
+ this->infer_abiflags(relobj, &header_abiflags);
+
+ if (sec_abiflags == NULL)
+ {
+ // If there is no input .MIPS.abiflags section, use abiflags created
+ // from elf header.
+ *abiflags = header_abiflags;
+ return;
+ }
+
+ this->has_abiflags_section_ = true;
+
+ // It is not possible to infer the correct ISA revision for R3 or R5
+ // so drop down to R2 for the checks.
+ unsigned char isa_rev = sec_abiflags->isa_rev;
+ if (isa_rev == 3 || isa_rev == 5)
+ isa_rev = 2;
+
+ // Check compatibility between abiflags created from elf header
+ // and abiflags from .MIPS.abiflags section in this object file.
+ if (this->level_rev(sec_abiflags->isa_level, isa_rev)
+ < this->level_rev(header_abiflags.isa_level, header_abiflags.isa_rev))
+ gold_warning(_("%s: Inconsistent ISA between e_flags and .MIPS.abiflags"),
+ relobj->name().c_str());
+ if (header_abiflags.fp_abi != elfcpp::Val_GNU_MIPS_ABI_FP_ANY
+ && sec_abiflags->fp_abi != header_abiflags.fp_abi)
+ gold_warning(_("%s: Inconsistent FP ABI between .gnu.attributes and "
+ ".MIPS.abiflags"), relobj->name().c_str());
+ if ((sec_abiflags->ases & header_abiflags.ases) != header_abiflags.ases)
+ gold_warning(_("%s: Inconsistent ASEs between e_flags and .MIPS.abiflags"),
+ relobj->name().c_str());
+ // The isa_ext is allowed to be an extension of what can be inferred
+ // from e_flags.
+ if (!this->mips_mach_extends(this->mips_isa_ext_mach(header_abiflags.isa_ext),
+ this->mips_isa_ext_mach(sec_abiflags->isa_ext)))
+ gold_warning(_("%s: Inconsistent ISA extensions between e_flags and "
+ ".MIPS.abiflags"), relobj->name().c_str());
+ if (sec_abiflags->flags2 != 0)
+ gold_warning(_("%s: Unexpected flag in the flags2 field of "
+ ".MIPS.abiflags (0x%x)"), relobj->name().c_str(),
+ sec_abiflags->flags2);
+ // Use abiflags from .MIPS.abiflags section.
+ *abiflags = *sec_abiflags;
+}
+
+// Return the meaning of fp_abi, or "unknown" if not known.
+
+template<int size, bool big_endian>
+const char*
+Target_mips<size, big_endian>::fp_abi_string(int fp)
+{
+ switch (fp)
+ {
+ case elfcpp::Val_GNU_MIPS_ABI_FP_DOUBLE:
+ return "-mdouble-float";
+ case elfcpp::Val_GNU_MIPS_ABI_FP_SINGLE:
+ return "-msingle-float";
+ case elfcpp::Val_GNU_MIPS_ABI_FP_SOFT:
+ return "-msoft-float";
+ case elfcpp::Val_GNU_MIPS_ABI_FP_OLD_64:
+ return _("-mips32r2 -mfp64 (12 callee-saved)");
+ case elfcpp::Val_GNU_MIPS_ABI_FP_XX:
+ return "-mfpxx";
+ case elfcpp::Val_GNU_MIPS_ABI_FP_64:
+ return "-mgp32 -mfp64";
+ case elfcpp::Val_GNU_MIPS_ABI_FP_64A:
+ return "-mgp32 -mfp64 -mno-odd-spreg";
+ default:
+ return "unknown";
+ }
+}
+
+// Select fp_abi.
+
+template<int size, bool big_endian>
+int
+Target_mips<size, big_endian>::select_fp_abi(const std::string& name, int in_fp,
+ int out_fp)
+{
+ if (in_fp == out_fp)
+ return out_fp;
+
+ if (out_fp == elfcpp::Val_GNU_MIPS_ABI_FP_ANY)
+ return in_fp;
+ else if (out_fp == elfcpp::Val_GNU_MIPS_ABI_FP_XX
+ && (in_fp == elfcpp::Val_GNU_MIPS_ABI_FP_DOUBLE
+ || in_fp == elfcpp::Val_GNU_MIPS_ABI_FP_64
+ || in_fp == elfcpp::Val_GNU_MIPS_ABI_FP_64A))
+ return in_fp;
+ else if (in_fp == elfcpp::Val_GNU_MIPS_ABI_FP_XX
+ && (out_fp == elfcpp::Val_GNU_MIPS_ABI_FP_DOUBLE
+ || out_fp == elfcpp::Val_GNU_MIPS_ABI_FP_64
+ || out_fp == elfcpp::Val_GNU_MIPS_ABI_FP_64A))
+ return out_fp; // Keep the current setting.
+ else if (out_fp == elfcpp::Val_GNU_MIPS_ABI_FP_64A
+ && in_fp == elfcpp::Val_GNU_MIPS_ABI_FP_64)
+ return in_fp;
+ else if (in_fp == elfcpp::Val_GNU_MIPS_ABI_FP_64A
+ && out_fp == elfcpp::Val_GNU_MIPS_ABI_FP_64)
+ return out_fp; // Keep the current setting.
+ else if (in_fp != elfcpp::Val_GNU_MIPS_ABI_FP_ANY)
+ gold_warning(_("%s: FP ABI %s is incompatible with %s"), name.c_str(),
+ fp_abi_string(in_fp), fp_abi_string(out_fp));
+ return out_fp;
+}
+
+// Merge attributes from input object.
+
+template<int size, bool big_endian>
+void
+Target_mips<size, big_endian>::merge_obj_attributes(const std::string& name,
+ const Attributes_section_data* pasd)
+{
+ // Return if there is no attributes section data.
+ if (pasd == NULL)
+ return;
+
+ // If output has no object attributes, just copy.
+ if (this->attributes_section_data_ == NULL)
+ {
+ this->attributes_section_data_ = new Attributes_section_data(*pasd);
+ return;
+ }
+
+ Object_attribute* out_attr = this->attributes_section_data_->known_attributes(
+ Object_attribute::OBJ_ATTR_GNU);
+
+ out_attr[elfcpp::Tag_GNU_MIPS_ABI_FP].set_type(1);
+ out_attr[elfcpp::Tag_GNU_MIPS_ABI_FP].set_int_value(this->abiflags_->fp_abi);
+
+ // Merge Tag_compatibility attributes and any common GNU ones.
+ this->attributes_section_data_->merge(name.c_str(), pasd);
+}
+
+// Merge abiflags from input object.
+
+template<int size, bool big_endian>
+void
+Target_mips<size, big_endian>::merge_obj_abiflags(const std::string& name,
+ Mips_abiflags<big_endian>* in_abiflags)
+{
+ // If output has no abiflags, just copy.
+ if (this->abiflags_ == NULL)
+ {
+ this->abiflags_ = new Mips_abiflags<big_endian>(*in_abiflags);
+ return;
+ }
+
+ this->abiflags_->fp_abi = this->select_fp_abi(name, in_abiflags->fp_abi,
+ this->abiflags_->fp_abi);
+
+ // Merge abiflags.
+ this->abiflags_->isa_level = std::max(this->abiflags_->isa_level,
+ in_abiflags->isa_level);
+ this->abiflags_->isa_rev = std::max(this->abiflags_->isa_rev,
+ in_abiflags->isa_rev);
+ this->abiflags_->gpr_size = std::max(this->abiflags_->gpr_size,
+ in_abiflags->gpr_size);
+ this->abiflags_->cpr1_size = std::max(this->abiflags_->cpr1_size,
+ in_abiflags->cpr1_size);
+ this->abiflags_->cpr2_size = std::max(this->abiflags_->cpr2_size,
+ in_abiflags->cpr2_size);
+ this->abiflags_->ases |= in_abiflags->ases;
+ this->abiflags_->flags1 |= in_abiflags->flags1;
+}
+