llvm-project/lld/ELF/Arch/MipsArchTree.cpp

390 lines
12 KiB
C++
Raw Normal View History

//===- MipsArchTree.cpp --------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===---------------------------------------------------------------------===//
//
// This file contains a helper function for the Writer.
//
//===---------------------------------------------------------------------===//
#include "InputFiles.h"
#include "SymbolTable.h"
#include "Writer.h"
#include "lld/Common/ErrorHandler.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Object/ELF.h"
#include "llvm/Support/MipsABIFlags.h"
using namespace llvm;
using namespace llvm::object;
using namespace llvm::ELF;
using namespace lld;
using namespace lld::elf;
namespace {
struct ArchTreeEdge {
uint32_t Child;
uint32_t Parent;
};
struct FileFlags {
InputFile *File;
uint32_t Flags;
};
} // namespace
static StringRef getAbiName(uint32_t Flags) {
switch (Flags) {
case 0:
return "n64";
case EF_MIPS_ABI2:
return "n32";
case EF_MIPS_ABI_O32:
return "o32";
case EF_MIPS_ABI_O64:
return "o64";
case EF_MIPS_ABI_EABI32:
return "eabi32";
case EF_MIPS_ABI_EABI64:
return "eabi64";
default:
return "unknown";
}
}
static StringRef getNanName(bool IsNan2008) {
return IsNan2008 ? "2008" : "legacy";
}
static StringRef getFpName(bool IsFp64) { return IsFp64 ? "64" : "32"; }
static void checkFlags(ArrayRef<FileFlags> Files) {
assert(!Files.empty() && "expected non-empty file list");
uint32_t ABI = Files[0].Flags & (EF_MIPS_ABI | EF_MIPS_ABI2);
bool Nan = Files[0].Flags & EF_MIPS_NAN2008;
bool Fp = Files[0].Flags & EF_MIPS_FP64;
for (const FileFlags &F : Files) {
if (Config->Is64 && F.Flags & EF_MIPS_MICROMIPS)
error(toString(F.File) + ": microMIPS 64-bit is not supported");
uint32_t ABI2 = F.Flags & (EF_MIPS_ABI | EF_MIPS_ABI2);
if (ABI != ABI2)
error(toString(F.File) + ": ABI '" + getAbiName(ABI2) +
"' is incompatible with target ABI '" + getAbiName(ABI) + "'");
bool Nan2 = F.Flags & EF_MIPS_NAN2008;
if (Nan != Nan2)
error(toString(F.File) + ": -mnan=" + getNanName(Nan2) +
" is incompatible with target -mnan=" + getNanName(Nan));
bool Fp2 = F.Flags & EF_MIPS_FP64;
if (Fp != Fp2)
error(toString(F.File) + ": -mfp" + getFpName(Fp2) +
" is incompatible with target -mfp" + getFpName(Fp));
}
}
static uint32_t getMiscFlags(ArrayRef<FileFlags> Files) {
uint32_t Ret = 0;
for (const FileFlags &F : Files)
Ret |= F.Flags &
(EF_MIPS_ABI | EF_MIPS_ABI2 | EF_MIPS_ARCH_ASE | EF_MIPS_NOREORDER |
EF_MIPS_MICROMIPS | EF_MIPS_NAN2008 | EF_MIPS_32BITMODE);
return Ret;
}
static uint32_t getPicFlags(ArrayRef<FileFlags> Files) {
// Check PIC/non-PIC compatibility.
bool IsPic = Files[0].Flags & (EF_MIPS_PIC | EF_MIPS_CPIC);
for (const FileFlags &F : Files.slice(1)) {
bool IsPic2 = F.Flags & (EF_MIPS_PIC | EF_MIPS_CPIC);
if (IsPic && !IsPic2)
warn(toString(F.File) +
": linking non-abicalls code with abicalls code " +
toString(Files[0].File));
if (!IsPic && IsPic2)
warn(toString(F.File) +
": linking abicalls code with non-abicalls code " +
toString(Files[0].File));
}
// Compute the result PIC/non-PIC flag.
uint32_t Ret = Files[0].Flags & (EF_MIPS_PIC | EF_MIPS_CPIC);
for (const FileFlags &F : Files.slice(1))
Ret &= F.Flags & (EF_MIPS_PIC | EF_MIPS_CPIC);
// PIC code is inherently CPIC and may not set CPIC flag explicitly.
if (Ret & EF_MIPS_PIC)
Ret |= EF_MIPS_CPIC;
return Ret;
}
static ArchTreeEdge ArchTree[] = {
// MIPS32R6 and MIPS64R6 are not compatible with other extensions
// MIPS64R2 extensions.
{EF_MIPS_ARCH_64R2 | EF_MIPS_MACH_OCTEON3, EF_MIPS_ARCH_64R2},
{EF_MIPS_ARCH_64R2 | EF_MIPS_MACH_OCTEON2, EF_MIPS_ARCH_64R2},
{EF_MIPS_ARCH_64R2 | EF_MIPS_MACH_OCTEON, EF_MIPS_ARCH_64R2},
{EF_MIPS_ARCH_64R2 | EF_MIPS_MACH_LS3A, EF_MIPS_ARCH_64R2},
// MIPS64 extensions.
{EF_MIPS_ARCH_64 | EF_MIPS_MACH_SB1, EF_MIPS_ARCH_64},
{EF_MIPS_ARCH_64 | EF_MIPS_MACH_XLR, EF_MIPS_ARCH_64},
{EF_MIPS_ARCH_64R2, EF_MIPS_ARCH_64},
// MIPS V extensions.
{EF_MIPS_ARCH_64, EF_MIPS_ARCH_5},
// R5000 extensions.
{EF_MIPS_ARCH_4 | EF_MIPS_MACH_5500, EF_MIPS_ARCH_4 | EF_MIPS_MACH_5400},
// MIPS IV extensions.
{EF_MIPS_ARCH_4 | EF_MIPS_MACH_5400, EF_MIPS_ARCH_4},
{EF_MIPS_ARCH_4 | EF_MIPS_MACH_9000, EF_MIPS_ARCH_4},
{EF_MIPS_ARCH_5, EF_MIPS_ARCH_4},
// VR4100 extensions.
{EF_MIPS_ARCH_3 | EF_MIPS_MACH_4111, EF_MIPS_ARCH_3 | EF_MIPS_MACH_4100},
{EF_MIPS_ARCH_3 | EF_MIPS_MACH_4120, EF_MIPS_ARCH_3 | EF_MIPS_MACH_4100},
// MIPS III extensions.
{EF_MIPS_ARCH_3 | EF_MIPS_MACH_4010, EF_MIPS_ARCH_3},
{EF_MIPS_ARCH_3 | EF_MIPS_MACH_4100, EF_MIPS_ARCH_3},
{EF_MIPS_ARCH_3 | EF_MIPS_MACH_4650, EF_MIPS_ARCH_3},
{EF_MIPS_ARCH_3 | EF_MIPS_MACH_5900, EF_MIPS_ARCH_3},
{EF_MIPS_ARCH_3 | EF_MIPS_MACH_LS2E, EF_MIPS_ARCH_3},
{EF_MIPS_ARCH_3 | EF_MIPS_MACH_LS2F, EF_MIPS_ARCH_3},
{EF_MIPS_ARCH_4, EF_MIPS_ARCH_3},
// MIPS32 extensions.
{EF_MIPS_ARCH_32R2, EF_MIPS_ARCH_32},
// MIPS II extensions.
{EF_MIPS_ARCH_3, EF_MIPS_ARCH_2},
{EF_MIPS_ARCH_32, EF_MIPS_ARCH_2},
// MIPS I extensions.
{EF_MIPS_ARCH_1 | EF_MIPS_MACH_3900, EF_MIPS_ARCH_1},
{EF_MIPS_ARCH_2, EF_MIPS_ARCH_1},
};
static bool isArchMatched(uint32_t New, uint32_t Res) {
if (New == Res)
return true;
if (New == EF_MIPS_ARCH_32 && isArchMatched(EF_MIPS_ARCH_64, Res))
return true;
if (New == EF_MIPS_ARCH_32R2 && isArchMatched(EF_MIPS_ARCH_64R2, Res))
return true;
for (const auto &Edge : ArchTree) {
if (Res == Edge.Child) {
Res = Edge.Parent;
if (Res == New)
return true;
}
}
return false;
}
static StringRef getMachName(uint32_t Flags) {
switch (Flags & EF_MIPS_MACH) {
case EF_MIPS_MACH_NONE:
return "";
case EF_MIPS_MACH_3900:
return "r3900";
case EF_MIPS_MACH_4010:
return "r4010";
case EF_MIPS_MACH_4100:
return "r4100";
case EF_MIPS_MACH_4650:
return "r4650";
case EF_MIPS_MACH_4120:
return "r4120";
case EF_MIPS_MACH_4111:
return "r4111";
case EF_MIPS_MACH_5400:
return "vr5400";
case EF_MIPS_MACH_5900:
return "vr5900";
case EF_MIPS_MACH_5500:
return "vr5500";
case EF_MIPS_MACH_9000:
return "rm9000";
case EF_MIPS_MACH_LS2E:
return "loongson2e";
case EF_MIPS_MACH_LS2F:
return "loongson2f";
case EF_MIPS_MACH_LS3A:
return "loongson3a";
case EF_MIPS_MACH_OCTEON:
return "octeon";
case EF_MIPS_MACH_OCTEON2:
return "octeon2";
case EF_MIPS_MACH_OCTEON3:
return "octeon3";
case EF_MIPS_MACH_SB1:
return "sb1";
case EF_MIPS_MACH_XLR:
return "xlr";
default:
return "unknown machine";
}
}
static StringRef getArchName(uint32_t Flags) {
switch (Flags & EF_MIPS_ARCH) {
case EF_MIPS_ARCH_1:
return "mips1";
case EF_MIPS_ARCH_2:
return "mips2";
case EF_MIPS_ARCH_3:
return "mips3";
case EF_MIPS_ARCH_4:
return "mips4";
case EF_MIPS_ARCH_5:
return "mips5";
case EF_MIPS_ARCH_32:
return "mips32";
case EF_MIPS_ARCH_64:
return "mips64";
case EF_MIPS_ARCH_32R2:
return "mips32r2";
case EF_MIPS_ARCH_64R2:
return "mips64r2";
case EF_MIPS_ARCH_32R6:
return "mips32r6";
case EF_MIPS_ARCH_64R6:
return "mips64r6";
default:
return "unknown arch";
}
}
static std::string getFullArchName(uint32_t Flags) {
StringRef Arch = getArchName(Flags);
StringRef Mach = getMachName(Flags);
if (Mach.empty())
return Arch.str();
return (Arch + " (" + Mach + ")").str();
}
// There are (arguably too) many MIPS ISAs out there. Their relationships
// can be represented as a forest. If all input files have ISAs which
// reachable by repeated proceeding from the single child to the parent,
// these input files are compatible. In that case we need to return "highest"
// ISA. If there are incompatible input files, we show an error.
// For example, mips1 is a "parent" of mips2 and such files are compatible.
// Output file gets EF_MIPS_ARCH_2 flag. From the other side mips3 and mips32
// are incompatible because nor mips3 is a parent for misp32, nor mips32
// is a parent for mips3.
static uint32_t getArchFlags(ArrayRef<FileFlags> Files) {
uint32_t Ret = Files[0].Flags & (EF_MIPS_ARCH | EF_MIPS_MACH);
for (const FileFlags &F : Files.slice(1)) {
uint32_t New = F.Flags & (EF_MIPS_ARCH | EF_MIPS_MACH);
// Check ISA compatibility.
if (isArchMatched(New, Ret))
continue;
if (!isArchMatched(Ret, New)) {
error("incompatible target ISA:\n>>> " + toString(Files[0].File) + ": " +
getFullArchName(Ret) + "\n>>> " + toString(F.File) + ": " +
getFullArchName(New));
return 0;
}
Ret = New;
}
return Ret;
}
template <class ELFT> uint32_t elf::calcMipsEFlags() {
std::vector<FileFlags> V;
for (InputFile *F : ObjectFiles)
V.push_back({F, cast<ObjFile<ELFT>>(F)->getObj().getHeader()->e_flags});
if (V.empty())
return 0;
checkFlags(V);
return getMiscFlags(V) | getPicFlags(V) | getArchFlags(V);
}
static int compareMipsFpAbi(uint8_t FpA, uint8_t FpB) {
if (FpA == FpB)
return 0;
if (FpB == Mips::Val_GNU_MIPS_ABI_FP_ANY)
return 1;
if (FpB == Mips::Val_GNU_MIPS_ABI_FP_64A &&
FpA == Mips::Val_GNU_MIPS_ABI_FP_64)
return 1;
if (FpB != Mips::Val_GNU_MIPS_ABI_FP_XX)
return -1;
if (FpA == Mips::Val_GNU_MIPS_ABI_FP_DOUBLE ||
FpA == Mips::Val_GNU_MIPS_ABI_FP_64 ||
FpA == Mips::Val_GNU_MIPS_ABI_FP_64A)
return 1;
return -1;
}
static StringRef getMipsFpAbiName(uint8_t FpAbi) {
switch (FpAbi) {
case Mips::Val_GNU_MIPS_ABI_FP_ANY:
return "any";
case Mips::Val_GNU_MIPS_ABI_FP_DOUBLE:
return "-mdouble-float";
case Mips::Val_GNU_MIPS_ABI_FP_SINGLE:
return "-msingle-float";
case Mips::Val_GNU_MIPS_ABI_FP_SOFT:
return "-msoft-float";
case Mips::Val_GNU_MIPS_ABI_FP_OLD_64:
return "-mgp32 -mfp64 (old)";
case Mips::Val_GNU_MIPS_ABI_FP_XX:
return "-mfpxx";
case Mips::Val_GNU_MIPS_ABI_FP_64:
return "-mgp32 -mfp64";
case Mips::Val_GNU_MIPS_ABI_FP_64A:
return "-mgp32 -mfp64 -mno-odd-spreg";
default:
return "unknown";
}
}
uint8_t elf::getMipsFpAbiFlag(uint8_t OldFlag, uint8_t NewFlag,
StringRef FileName) {
if (compareMipsFpAbi(NewFlag, OldFlag) >= 0)
return NewFlag;
if (compareMipsFpAbi(OldFlag, NewFlag) < 0)
error(FileName + ": floating point ABI '" + getMipsFpAbiName(NewFlag) +
"' is incompatible with target floating point ABI '" +
getMipsFpAbiName(OldFlag) + "'");
return OldFlag;
}
template <class ELFT> static bool isN32Abi(const InputFile *F) {
if (auto *EF = dyn_cast<ELFFileBase>(F))
return EF->template getObj<ELFT>().getHeader()->e_flags & EF_MIPS_ABI2;
return false;
}
bool elf::isMipsN32Abi(const InputFile *F) {
switch (Config->EKind) {
case ELF32LEKind:
return isN32Abi<ELF32LE>(F);
case ELF32BEKind:
return isN32Abi<ELF32BE>(F);
case ELF64LEKind:
return isN32Abi<ELF64LE>(F);
case ELF64BEKind:
return isN32Abi<ELF64BE>(F);
default:
llvm_unreachable("unknown Config->EKind");
}
}
bool elf::isMicroMips() { return Config->EFlags & EF_MIPS_MICROMIPS; }
bool elf::isMipsR6() {
uint32_t Arch = Config->EFlags & EF_MIPS_ARCH;
return Arch == EF_MIPS_ARCH_32R6 || Arch == EF_MIPS_ARCH_64R6;
}
template uint32_t elf::calcMipsEFlags<ELF32LE>();
template uint32_t elf::calcMipsEFlags<ELF32BE>();
template uint32_t elf::calcMipsEFlags<ELF64LE>();
template uint32_t elf::calcMipsEFlags<ELF64BE>();