Index: ELF/Arch/ARM.cpp =================================================================== --- ELF/Arch/ARM.cpp +++ ELF/Arch/ARM.cpp @@ -39,7 +39,9 @@ void addPltSymbols(InputSectionBase *IS, uint64_t Off) const override; void addPltHeaderSymbols(InputSectionBase *ISD) const override; bool needsThunk(RelExpr Expr, uint32_t RelocType, const InputFile *File, - const SymbolBody &S) const override; + uint64_t BranchAddr, const SymbolBody &S) const override; + bool inBranchRange(uint32_t RelocType, uint64_t Src, + uint64_t Dst) const override; void relocateOne(uint8_t *Loc, uint32_t Type, uint64_t Val) const override; }; } // namespace @@ -61,6 +63,10 @@ // ARM uses Variant 1 TLS TcbSize = 8; NeedsThunks = true; + // Thumb unconditional branch range on system with Thumb2 branch encoding + ThunkSectionSpacing = 0x1000000; + // Allow for 16384 12 byte Thunks per ThunkSectionSpacing + ThunkSectionSize = 0x30000; } RelExpr ARM::getRelExpr(uint32_t Type, const SymbolBody &S, @@ -188,7 +194,7 @@ } bool ARM::needsThunk(RelExpr Expr, uint32_t RelocType, const InputFile *File, - const SymbolBody &S) const { + uint64_t BranchAddr, const SymbolBody &S) const { // If S is an undefined weak symbol in an executable we don't need a Thunk. // In a DSO calls to undefined symbols, including weak ones get PLT entries // which may need a thunk. @@ -206,18 +212,69 @@ // Otherwise we need to interwork if Symbol has bit 0 set (Thumb). if (Expr == R_PC && ((S.getVA() & 1) == 1)) return true; - break; + // Fall through + case R_ARM_CALL: { + uint64_t Dst = (Expr == R_PLT_PC) ? S.getPltVA() : S.getVA(); + return !inBranchRange(RelocType, BranchAddr, Dst); + } case R_ARM_THM_JUMP19: case R_ARM_THM_JUMP24: // Source is Thumb, all PLT entries are ARM so interworking is required. // Otherwise we need to interwork if Symbol has bit 0 clear (ARM). if (Expr == R_PLT_PC || ((S.getVA() & 1) == 0)) return true; - break; + // Fall through + case R_ARM_THM_CALL: { + uint64_t Dst = (Expr == R_PLT_PC) ? S.getPltVA() : S.getVA(); + return !inBranchRange(RelocType, BranchAddr, Dst); + } } return false; } +bool ARM::inBranchRange(uint32_t RelocType, uint64_t Src, uint64_t Dst) const { + uint64_t Range; + uint64_t InstrSize; + switch (RelocType) { + case R_ARM_PC24: + case R_ARM_PLT32: + case R_ARM_JUMP24: + case R_ARM_CALL: + Range = 0x2000000; + InstrSize = 4; + break; + case R_ARM_THM_JUMP19: + Range = 0x100000; + InstrSize = 2; + break; + case R_ARM_THM_JUMP24: + case R_ARM_THM_CALL: + Range = 0x1000000; + InstrSize = 2; + break; + default: + return true; + } + // PC at Src is 2 instructions ahead, immediate of branch is signed + if (Src > Dst) + Range -= 2 * InstrSize; + else + Range += InstrSize; + + if ((Dst & 0x1) == 0) + // Destination is ARM, if ARM caller then Src is already 4-byte aligned. + // If Thumb Caller (BLX) the Src address has bottom 2 bits cleared to ensure + // destination will be 4 byte aligned. + Src &= ~0x3; + else + // Bit 0 == 1 denotes Thumb state, it is not part of the range + Dst &= ~0x1; + + uint64_t Distance = (Src > Dst) ? Src - Dst : Dst - Src; + + return Distance <= Range; +} + void ARM::relocateOne(uint8_t *Loc, uint32_t Type, uint64_t Val) const { switch (Type) { case R_ARM_ABS32: Index: ELF/Arch/Mips.cpp =================================================================== --- ELF/Arch/Mips.cpp +++ ELF/Arch/Mips.cpp @@ -38,7 +38,7 @@ void writePlt(uint8_t *Buf, uint64_t GotPltEntryAddr, uint64_t PltEntryAddr, int32_t Index, unsigned RelOff) const override; bool needsThunk(RelExpr Expr, uint32_t RelocType, const InputFile *File, - const SymbolBody &S) const override; + uint64_t BranchAddr, const SymbolBody &S) const override; void relocateOne(uint8_t *Loc, uint32_t Type, uint64_t Val) const override; bool usesOnlyLowPageBits(uint32_t Type) const override; }; @@ -229,7 +229,7 @@ template bool MIPS::needsThunk(RelExpr Expr, uint32_t Type, const InputFile *File, - const SymbolBody &S) const { + uint64_t BranchAddr, const SymbolBody &S) const { // Any MIPS PIC code function is invoked with its address in register $t9. // So if we have a branch instruction from non-PIC code to the PIC one // we cannot make the jump directly and need to create a small stubs Index: ELF/LinkerScript.h =================================================================== --- ELF/LinkerScript.h +++ ELF/LinkerScript.h @@ -281,8 +281,9 @@ void assignOffsets(OutputSectionCommand *Cmd); void createOrphanCommands(); void processNonSectionCommands(); - void assignAddresses(std::vector &Phdrs); - + void resetAddressState(); + void assignAddresses(); + void allocateHeaders(std::vector &Phdrs); void addSymbol(SymbolAssignment *Cmd); void processCommands(OutputSectionFactory &Factory); Index: ELF/LinkerScript.cpp =================================================================== --- ELF/LinkerScript.cpp +++ ELF/LinkerScript.cpp @@ -779,22 +779,26 @@ } } -static bool -allocateHeaders(std::vector &Phdrs, - ArrayRef OutputSectionCommands, - uint64_t Min) { +void LinkerScript::allocateHeaders(std::vector &Phdrs) { + uint64_t Min = std::numeric_limits::max(); + for (OutputSectionCommand *Cmd : OutputSectionCommands) { + OutputSection *Sec = Cmd->Sec; + if (Sec->Flags & SHF_ALLOC) + Min = std::min(Min, Sec->Addr); + } + auto FirstPTLoad = std::find_if(Phdrs.begin(), Phdrs.end(), [](const PhdrEntry &E) { return E.p_type == PT_LOAD; }); if (FirstPTLoad == Phdrs.end()) - return false; + return; uint64_t HeaderSize = getHeaderSize(); if (HeaderSize <= Min || Script->hasPhdrsCommands()) { Min = alignDown(Min - HeaderSize, Config->MaxPageSize); Out::ElfHeader->Addr = Min; Out::ProgramHeaders->Addr = Min + Out::ElfHeader->Size; - return true; + return; } assert(FirstPTLoad->First == Out::ElfHeader); @@ -822,12 +826,25 @@ }); if (PhdrI != Phdrs.end()) Phdrs.erase(PhdrI); - return false; } -void LinkerScript::assignAddresses(std::vector &Phdrs) { +// Reset the members associated with address assigment to their initial values +// this permits addressAssignment to be run again. +void LinkerScript::resetAddressState() { + LMAOffset = 0; + CurOutSec = nullptr; + CurMemRegion = nullptr; + ThreadBssOffset = 0; + for (auto &MRI : Opt.MemoryRegions) { + MemoryRegion &MR = MRI.second; + MR.Offset = MR.Origin; + } +} + +void LinkerScript::assignAddresses() { // Assign addresses as instructed by linker script SECTIONS sub-commands. Dot = 0; + resetAddressState(); ErrorOnMissingSection = true; switchTo(Aether); @@ -845,15 +862,6 @@ auto *Cmd = cast(Base); assignOffsets(Cmd); } - - uint64_t MinVA = std::numeric_limits::max(); - for (OutputSectionCommand *Cmd : OutputSectionCommands) { - OutputSection *Sec = Cmd->Sec; - if (Sec->Flags & SHF_ALLOC) - MinVA = std::min(MinVA, Sec->Addr); - } - - allocateHeaders(Phdrs, OutputSectionCommands, MinVA); } // Creates program headers as instructed by PHDRS linker script command. Index: ELF/Relocations.h =================================================================== --- ELF/Relocations.h +++ ELF/Relocations.h @@ -133,36 +133,46 @@ private: void mergeThunks(); - ThunkSection *getOSThunkSec(OutputSection *OS, - std::vector *ISR); + ThunkSection *getISRThunkSec(OutputSection *OS, InputSection *IS, + std::vector *ISR, uint32_t Type, + uint64_t Src); ThunkSection *getISThunkSec(InputSection *IS, OutputSection *OS); + void + createInitialThunkSections(ArrayRef OutputSections); void forEachExecInputSection( ArrayRef OutputSections, std::function *, InputSection *)> Fn); - std::pair getThunk(SymbolBody &Body, uint32_t Type); + std::pair getThunk(SymbolBody &Body, uint32_t Type, + uint64_t Src); ThunkSection *addThunkSection(OutputSection *OS, std::vector *, uint64_t Off); - // Track Symbols that already have a Thunk - llvm::DenseMap ThunkedSymbols; + bool normalizeExistingThunk(Relocation &Rel, uint64_t Src); + + // Record all the available Thunks for a Symbol + llvm::DenseMap> ThunkedSymbols; // Find a Thunk from the Thunks symbol definition, we can use this to find // the Thunk from a relocation to the Thunks symbol definition. llvm::DenseMap Thunks; - // Track InputSections that have a ThunkSection placed in front + // Track InputSections that have an inline ThunkSection placed in front + // an inline ThunkSection may have control fall through to the section below + // so we need to make sure that there is only one of them. + // The Mips LA25 Thunk is an example of an inline ThunkSection. llvm::DenseMap ThunkedSections; - // All the ThunkSections that we have created, organised by OutputSection + // All the ThunkSections that we have created, organised by InputSectionRange // will contain a mix of ThunkSections that have been created this pass, and - // ThunkSections that have been merged into the OutputSection on previous + // ThunkSections that have been merged into InputSectionRanges on previous // passes std::map *, std::vector> ThunkSections; - // The ThunkSection for this vector of InputSections - ThunkSection *CurTS; + // All the ThunkSections that we have created, organised by InputSectionRange + std::map *, std::vector> + NewThunkSections; }; // Return a int64_t to make sure we get the sign extension out of the way as Index: ELF/Relocations.cpp =================================================================== --- ELF/Relocations.cpp +++ ELF/Relocations.cpp @@ -969,10 +969,19 @@ // offsets. // This may invalidate any output section offsets stored outside of InputSection void ThunkCreator::mergeThunks() { - for (auto &KV : ThunkSections) { + for (auto &KV : NewThunkSections) { std::vector *ISR = KV.first; std::vector &Thunks = KV.second; + // Remove ThunkSections that contain no Thunks + auto ThunkBegin = Thunks.begin(); + auto ThunkEnd = Thunks.end(); + ThunkEnd = std::remove_if(ThunkBegin, ThunkEnd, [](const ThunkSection *TS) { + return TS->getSize() == 0; + }); + if (ThunkBegin == ThunkEnd) + continue; + // Order Thunks in ascending OutSecOff auto ThunkCmp = [](const ThunkSection *A, const ThunkSection *B) { return A->OutSecOff < B->OutSecOff; @@ -986,12 +995,18 @@ // std::merge requires a strict weak ordering. if (A->OutSecOff < B->OutSecOff) return true; - if (A->OutSecOff == B->OutSecOff) + if (A->OutSecOff == B->OutSecOff) { + auto *TA = dyn_cast(A); + auto *TB = dyn_cast(B); // Check if Thunk is immediately before any specific Target InputSection // for example Mips LA25 Thunks. - if (auto *TA = dyn_cast(A)) - if (TA && TA->getTargetInputSection() == B) - return true; + if (TA && TA->getTargetInputSection() == B) + return true; + else if (TA && !TB && !TA->getTargetInputSection()) + // In general place Thunk Sections without specific targets before + // non-Thunk Sections + return true; + } return false; }; std::merge(ISR->begin(), ISR->end(), Thunks.begin(), Thunks.end(), @@ -1000,18 +1015,22 @@ } } -ThunkSection *ThunkCreator::getOSThunkSec(OutputSection *OS, - std::vector *ISR) { - if (CurTS == nullptr) { - uint32_t Off = 0; - for (auto *IS : OS->Sections) { - Off = IS->OutSecOff + IS->getSize(); - if ((IS->Flags & SHF_EXECINSTR) == 0) - break; - } - CurTS = addThunkSection(OS, ISR, Off); +ThunkSection *ThunkCreator::getISRThunkSec(OutputSection *OS, InputSection *IS, + std::vector *ISR, + uint32_t Type, uint64_t Src) { + // Use the simple heuristic of inserting the Thunk in the first ThunkSection + // in range of the caller. + for (ThunkSection *TS : ThunkSections[ISR]) { + uint64_t TSBase = OS->Addr + TS->OutSecOff; + uint64_t TSLimit = TSBase + TS->getSize(); + if (Target->inBranchRange(Type, Src, (Src > TSLimit) ? TSBase : TSLimit)) + return TS; } - return CurTS; + + // No suitable ThunkSection exists. This can happen when there is a branch + // with lower range than the ThunkSection spacing or when there are too + // many Thunks. Create a new ThunkSection. + return addThunkSection(OS, ISR, IS->OutSecOff); } ThunkSection *ThunkCreator::getISThunkSec(InputSection *IS, OutputSection *OS) { @@ -1024,7 +1043,7 @@ OutputSectionCommand *C = Script->getCmd(TOS); std::vector *Range = nullptr; for (BaseCommand *BC : C->Commands) - if (auto *ISD = dyn_cast (BC)) { + if (auto *ISD = dyn_cast(BC)) { InputSection *first = ISD->Sections.front(); InputSection *last = ISD->Sections.back(); if (IS->OutSecOff >= first->OutSecOff && @@ -1038,21 +1057,74 @@ return TS; } +// Create one or more ThunkSections per OS that can be used to place Thunks. +// We attempt to place the ThunkSections using the following desirable +// properties: +// - Within range of the maximum number of callers +// - Minimise the number of ThunkSections +// +// We follow a simple but conservative heuristic to place ThunkSections at +// offsets that are multiples of a Target specific branch range. +// For an InputSectionRange that is smaller than the range then a single +// ThunkSection at the end of the range will do. +void ThunkCreator::createInitialThunkSections( + ArrayRef OutputSections) { + bool NeedTrailingTS; + uint32_t Off; + uint32_t Limit; + InputSection *PrevIS = nullptr; + std::vector *PrevISR = nullptr; + + forEachExecInputSection(OutputSections, [&](OutputSection *OS, + std::vector *ISR, + InputSection *IS) { + if (ISR != PrevISR) { + NeedTrailingTS = true; + Off = 0; + Limit = IS->OutSecOff + (Target->ThunkSectionSpacing - + Target->ThunkSectionSize); + PrevIS = nullptr; + PrevISR = ISR; + } + Off = IS->OutSecOff + IS->getSize(); + if (Off >= Limit) { + uint32_t ThunkOff = (PrevIS == nullptr) ? IS->OutSecOff : + PrevIS->OutSecOff + PrevIS->getSize(); + addThunkSection(OS, ISR, ThunkOff); + NeedTrailingTS = false; + Limit = ThunkOff + Target->ThunkSectionSpacing; + } + PrevIS = IS; + + if (ISR->back() == IS && NeedTrailingTS) + addThunkSection(OS, ISR, Off); + }); +} + ThunkSection *ThunkCreator::addThunkSection(OutputSection *OS, std::vector *ISR, uint64_t Off) { auto *TS = make(OS, Off); ThunkSections[ISR].push_back(TS); + NewThunkSections[ISR].push_back(TS); return TS; } - std::pair ThunkCreator::getThunk(SymbolBody &Body, - uint32_t Type) { - auto res = ThunkedSymbols.insert({&Body, nullptr}); - if (res.second) - res.first->second = addThunk(Type, Body); - return std::make_pair(res.first->second, res.second); + uint32_t Type, + uint64_t Src) { + auto Res = ThunkedSymbols.insert({&Body, std::vector()}); + if (!Res.second) { + // Check existing Thunks for Body to see if they can be reused + for (Thunk *ET : Res.first->second) + if (ET->compatibleWith(Type) && + Target->inBranchRange(Type, Src, ET->ThunkSym->getVA())) + return {ET, false}; + } + // No existing compatible Thunk in range, create a new one + Thunk *T = addThunk(Type, Body); + Res.first->second.push_back(T); + return {T, true}; } // Call Fn on every executable InputSection accessed via the linker script @@ -1069,13 +1141,25 @@ if (OutputSectionCommand *C = Script->getCmd(OS)) for (BaseCommand *BC : C->Commands) if (auto *ISD = dyn_cast(BC)) { - CurTS = nullptr; for (InputSection* IS : ISD->Sections) Fn(OS, &ISD->Sections, IS); } } } +// If this Relocation is using a Thunk, use it if it hasn't gone out +// of range. If it has we revert back to the original Target. +bool ThunkCreator::normalizeExistingThunk(Relocation &Rel, uint64_t Src) { + if (Thunk *ET = Thunks.lookup(Rel.Sym)) { + if (Target->inBranchRange(Rel.Type, Src, Rel.Sym->getVA())) + return true; + Rel.Sym = &ET->Destination; + if (Rel.Sym->isInPlt()) + Rel.Expr = toPlt(Rel.Expr); + } + return false; +} + // Process all relocations from the InputSections that have been assigned // to OutputSections and redirect through Thunks if needed. // @@ -1083,13 +1167,17 @@ // each InputSection. It must be called before the static symbol table is // finalized. If any Thunks are added to an OutputSection the output section // offsets of the InputSections will change. -// -// FIXME: All Thunks are assumed to be in range of the relocation. Range -// extension Thunks are not yet supported. bool ThunkCreator::createThunks( ArrayRef OutputSections) { - if (Pass > 0) - ThunkSections.clear(); + bool AddressesChanged = false; + if (Pass > 0) { + NewThunkSections.clear(); + // With Thunk Size much smaller than branch range we expect to + // converge quickly, if we get to 10 something has gone wrong. + if (Pass == 10) + fatal("Thunk creation not converged"); + } else if (Target->ThunkSectionSpacing) + createInitialThunkSections(OutputSections); // Create all the Thunks and insert them into synthetic ThunkSections. The // ThunkSections are later inserted back into the OutputSection. @@ -1098,23 +1186,29 @@ // ThunkSections back into the OutputSection as ThunkSections are not always // inserted into the same OutputSection as the caller. forEachExecInputSection( - OutputSections, [&](OutputSection *OS, std::vector *ISR, + OutputSections, [&](OutputSection *OS, std::vector *ISR, InputSection *IS) { for (Relocation &Rel : IS->Relocations) { - SymbolBody &Body = *Rel.Sym; - if (Thunks.find(&Body) != Thunks.end() || - !Target->needsThunk(Rel.Expr, Rel.Type, IS->File, Body)) + uint64_t Src = OS->Addr + IS->OutSecOff + Rel.Offset; + + // If relocation to a valid existing Thunk, use it, if invalid Thunk + // redirect relocation to original target + if (Pass > 0 && normalizeExistingThunk(Rel, Src)) + continue; + + if (!Target->needsThunk(Rel.Expr, Rel.Type, IS->File, Src, *Rel.Sym)) continue; Thunk *T; bool IsNew; - std::tie(T, IsNew) = getThunk(Body, Rel.Type); + std::tie(T, IsNew) = getThunk(*Rel.Sym, Rel.Type, Src); if (IsNew) { + AddressesChanged = true; // Find or create a ThunkSection for the new Thunk ThunkSection *TS; if (auto *TIS = T->getTargetInputSection()) TS = getISThunkSec(TIS, OS); else - TS = getOSThunkSec(OS, ISR); + TS = getISRThunkSec(OS, IS, ISR, Rel.Type, Src); TS->addThunk(T); Thunks[T->ThunkSym] = T; } @@ -1126,7 +1220,7 @@ // Merge all created synthetic ThunkSections back into OutputSection mergeThunks(); ++Pass; - return !ThunkSections.empty(); + return AddressesChanged; } template void elf::scanRelocations(InputSectionBase &); Index: ELF/Target.h =================================================================== --- ELF/Target.h +++ ELF/Target.h @@ -50,7 +50,11 @@ // Decide whether a Thunk is needed for the relocation from File // targeting S. virtual bool needsThunk(RelExpr Expr, uint32_t RelocType, - const InputFile *File, const SymbolBody &S) const; + const InputFile *File, uint64_t BranchAddr, + const SymbolBody &S) const; + // Return true if we can reach Dst from Src with Relocation RelocType + virtual bool inBranchRange(uint32_t RelocType, uint64_t Src, + uint64_t Dst) const; virtual RelExpr getRelExpr(uint32_t Type, const SymbolBody &S, const uint8_t *Loc) const = 0; virtual void relocateOne(uint8_t *Loc, uint32_t Type, uint64_t Val) const = 0; @@ -70,6 +74,13 @@ // end of .got uint64_t GotBaseSymOff = 0; + // On systems with range extensions we place collections of Thunks at + // regular spacings that enable the majority of branches reach the Thunks. + uint32_t ThunkSectionSpacing = 0x0; + + // An estimate of size of the Thunks that will be created per ThunkSection + uint32_t ThunkSectionSize = 0x0; + uint32_t CopyRel; uint32_t GotRel; uint32_t PltRel; Index: ELF/Target.cpp =================================================================== --- ELF/Target.cpp +++ ELF/Target.cpp @@ -124,10 +124,16 @@ bool TargetInfo::usesOnlyLowPageBits(uint32_t Type) const { return false; } bool TargetInfo::needsThunk(RelExpr Expr, uint32_t RelocType, - const InputFile *File, const SymbolBody &S) const { + const InputFile *File, uint64_t BranchAddr, + const SymbolBody &S) const { return false; } +bool TargetInfo::inBranchRange(uint32_t RelocType, uint64_t Src, + uint64_t Dst) const { + return true; +} + void TargetInfo::writeIgotPlt(uint8_t *Buf, const SymbolBody &S) const { writeGotPlt(Buf, S); } Index: ELF/Thunks.h =================================================================== --- ELF/Thunks.h +++ ELF/Thunks.h @@ -27,7 +27,7 @@ // Thunks are assigned to synthetic ThunkSections class Thunk { public: - Thunk(const SymbolBody &Destination); + Thunk(SymbolBody &Destination); virtual ~Thunk(); virtual uint32_t size() const { return 0; } @@ -41,9 +41,13 @@ // a branch and fall through to the first Symbol in the Target. virtual InputSection *getTargetInputSection() const { return nullptr; } + // To reuse a Thunk the caller as identified by the RelocType must be + // compatible with it. + virtual bool compatibleWith(uint32_t RelocType) const { return true; } + // The alignment requirement for this Thunk, defaults to the size of the // typical code section alignment. - const SymbolBody &Destination; + SymbolBody &Destination; SymbolBody *ThunkSym; uint64_t Offset; uint32_t alignment = 4; Index: ELF/Thunks.cpp =================================================================== --- ELF/Thunks.cpp +++ ELF/Thunks.cpp @@ -52,48 +52,52 @@ // Source State, TargetState, Target Requirement, ABS or PI, Range class ARMV7ABSLongThunk final : public Thunk { public: - ARMV7ABSLongThunk(const SymbolBody &Dest) : Thunk(Dest) {} + ARMV7ABSLongThunk(SymbolBody &Dest) : Thunk(Dest) {} uint32_t size() const override { return 12; } void writeTo(uint8_t *Buf, ThunkSection &IS) const override; void addSymbols(ThunkSection &IS) override; + bool compatibleWith(uint32_t RelocType) const override; }; class ARMV7PILongThunk final : public Thunk { public: - ARMV7PILongThunk(const SymbolBody &Dest) : Thunk(Dest) {} + ARMV7PILongThunk(SymbolBody &Dest) : Thunk(Dest) {} uint32_t size() const override { return 16; } void writeTo(uint8_t *Buf, ThunkSection &IS) const override; void addSymbols(ThunkSection &IS) override; + bool compatibleWith(uint32_t RelocType) const override; }; class ThumbV7ABSLongThunk final : public Thunk { public: - ThumbV7ABSLongThunk(const SymbolBody &Dest) : Thunk(Dest) { + ThumbV7ABSLongThunk(SymbolBody &Dest) : Thunk(Dest) { this->alignment = 2; } uint32_t size() const override { return 10; } void writeTo(uint8_t *Buf, ThunkSection &IS) const override; void addSymbols(ThunkSection &IS) override; + bool compatibleWith(uint32_t RelocType) const override; }; class ThumbV7PILongThunk final : public Thunk { public: - ThumbV7PILongThunk(const SymbolBody &Dest) : Thunk(Dest) { + ThumbV7PILongThunk(SymbolBody &Dest) : Thunk(Dest) { this->alignment = 2; } uint32_t size() const override { return 12; } void writeTo(uint8_t *Buf, ThunkSection &IS) const override; void addSymbols(ThunkSection &IS) override; + bool compatibleWith(uint32_t RelocType) const override; }; // MIPS LA25 thunk class MipsThunk final : public Thunk { public: - MipsThunk(const SymbolBody &Dest) : Thunk(Dest) {} + MipsThunk(SymbolBody &Dest) : Thunk(Dest) {} uint32_t size() const override { return 16; } void writeTo(uint8_t *Buf, ThunkSection &IS) const override; @@ -128,6 +132,11 @@ addSyntheticLocal("$a", STT_NOTYPE, Offset, 0, &IS); } +bool ARMV7ABSLongThunk::compatibleWith(uint32_t RelocType) const { + // Thumb branch relocations can't use BLX + return RelocType != R_ARM_THM_JUMP19 && RelocType != R_ARM_THM_JUMP24; +} + void ThumbV7ABSLongThunk::writeTo(uint8_t *Buf, ThunkSection &IS) const { const uint8_t Data[] = { 0x40, 0xf2, 0x00, 0x0c, // movw ip, :lower16:S @@ -147,6 +156,12 @@ addSyntheticLocal("$t", STT_NOTYPE, Offset, 0, &IS); } +bool ThumbV7ABSLongThunk::compatibleWith(uint32_t RelocType) const { + // ARM branch relocations can't use BLX + return RelocType != R_ARM_JUMP24 && RelocType != R_ARM_PC24 && + RelocType != R_ARM_PLT32; +} + void ARMV7PILongThunk::writeTo(uint8_t *Buf, ThunkSection &IS) const { const uint8_t Data[] = { 0xf0, 0xcf, 0x0f, 0xe3, // P: movw ip,:lower16:S - (P + (L1-P) +8) @@ -168,6 +183,11 @@ addSyntheticLocal("$a", STT_NOTYPE, Offset, 0, &IS); } +bool ARMV7PILongThunk::compatibleWith(uint32_t RelocType) const { + // Thumb branch relocations can't use BLX + return RelocType != R_ARM_THM_JUMP19 && RelocType != R_ARM_THM_JUMP24; +} + void ThumbV7PILongThunk::writeTo(uint8_t *Buf, ThunkSection &IS) const { const uint8_t Data[] = { 0x4f, 0xf6, 0xf4, 0x7c, // P: movw ip,:lower16:S - (P + (L1-P) + 4) @@ -189,6 +209,12 @@ addSyntheticLocal("$t", STT_NOTYPE, Offset, 0, &IS); } +bool ThumbV7PILongThunk::compatibleWith(uint32_t RelocType) const { + // ARM branch relocations can't use BLX + return RelocType != R_ARM_JUMP24 && RelocType != R_ARM_PC24 && + RelocType != R_ARM_PLT32; +} + // Write MIPS LA25 thunk code to call PIC function from the non-PIC one. void MipsThunk::writeTo(uint8_t *Buf, ThunkSection &) const { uint64_t S = this->Destination.getVA(); @@ -211,7 +237,7 @@ return dyn_cast(DR->Section); } -Thunk::Thunk(const SymbolBody &D) : Destination(D), Offset(0) {} +Thunk::Thunk(SymbolBody &D) : Destination(D), Offset(0) {} Thunk::~Thunk() = default; @@ -224,11 +250,13 @@ case R_ARM_PC24: case R_ARM_PLT32: case R_ARM_JUMP24: + case R_ARM_CALL: if (Config->Pic) return make(S); return make(S); case R_ARM_THM_JUMP19: case R_ARM_THM_JUMP24: + case R_ARM_THM_CALL: if (Config->Pic) return make(S); return make(S); Index: ELF/Writer.cpp =================================================================== --- ELF/Writer.cpp +++ ELF/Writer.cpp @@ -218,7 +218,8 @@ OutputSectionCommands.begin(), OutputSectionCommands.end(), [](OutputSectionCommand *Cmd) { Cmd->maybeCompress(); }); - Script->assignAddresses(Phdrs); + Script->assignAddresses(); + Script->allocateHeaders(Phdrs); // Remove empty PT_LOAD to avoid causing the dynamic linker to try to mmap a // 0 sized region. This has to be done late since only after assignAddresses @@ -1286,11 +1287,11 @@ // are out of range. This will need to turn into a loop that converges // when no more Thunks are added ThunkCreator TC; - if (TC.createThunks(OutputSectionCommands)) { + Script->assignAddresses(); + while (TC.createThunks(OutputSectionCommands)) { applySynthetic({InX::MipsGot}, [](SyntheticSection *SS) { SS->updateAllocSize(); }); - if (TC.createThunks(OutputSectionCommands)) - fatal("All non-range thunks should be created in first call"); + Script->assignAddresses(); } } Index: test/ELF/arm-branch-error.s =================================================================== --- test/ELF/arm-branch-error.s +++ /dev/null @@ -1,19 +0,0 @@ -// RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %s -o %t -// RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %S/Inputs/far-arm-abs.s -o %tfar -// RUN: not ld.lld %t %tfar -o %t2 2>&1 | FileCheck %s -// REQUIRES: arm - .syntax unified - .section .text, "ax",%progbits - .globl _start - .balign 0x10000 - .type _start,%function -_start: - // address of too_far symbols are just out of range of ARM branch with - // 26-bit immediate field and an addend of -8 - bl too_far1 - b too_far2 - beq too_far3 - -// CHECK: R_ARM_CALL out of range -// CHECK-NEXT: R_ARM_JUMP24 out of range -// CHECK-NEXT: R_ARM_JUMP24 out of range Index: test/ELF/arm-branch-rangethunk.s =================================================================== --- /dev/null +++ test/ELF/arm-branch-rangethunk.s @@ -0,0 +1,34 @@ +// RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %s -o %t +// RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %S/Inputs/far-arm-abs.s -o %tfar +// RUN: ld.lld %t %tfar -o %t2 2>&1 +// RUN: llvm-objdump -d -triple=armv7a-none-linux-gnueabi %t2 | FileCheck %s +// REQUIRES: arm + .syntax unified + .section .text, "ax",%progbits + .globl _start + .balign 0x10000 + .type _start,%function +_start: + // address of too_far symbols are just out of range of ARM branch with + // 26-bit immediate field and an addend of -8 + bl too_far1 + b too_far2 + beq too_far3 + +// CHECK: Disassembly of section .text: +// CHECK-NEXT: _start: +// CHECK-NEXT: 20000: 01 00 00 eb bl #4 <__ARMv7ABSLongThunk_too_far1> +// CHECK-NEXT: 20004: 03 00 00 ea b #12 <__ARMv7ABSLongThunk_too_far2> +// CHECK-NEXT: 20008: 05 00 00 0a beq #20 <__ARMv7ABSLongThunk_too_far3> +// CHECK: __ARMv7ABSLongThunk_too_far1: +// CHECK-NEXT: 2000c: 08 c0 00 e3 movw r12, #8 +// CHECK-NEXT: 20010: 02 c2 40 e3 movt r12, #514 +// CHECK-NEXT: 20014: 1c ff 2f e1 bx r12 +// CHECK: __ARMv7ABSLongThunk_too_far2: +// CHECK-NEXT: 20018: 0c c0 00 e3 movw r12, #12 +// CHECK-NEXT: 2001c: 02 c2 40 e3 movt r12, #514 +// CHECK-NEXT: 20020: 1c ff 2f e1 bx r12 +// CHECK: __ARMv7ABSLongThunk_too_far3: +// CHECK-NEXT: 20024: 10 c0 00 e3 movw r12, #16 +// CHECK-NEXT: 20028: 02 c2 40 e3 movt r12, #514 +// CHECK-NEXT: 2002c: 1c ff 2f e1 bx r12 Index: test/ELF/arm-thumb-branch-error.s =================================================================== --- test/ELF/arm-thumb-branch-error.s +++ /dev/null @@ -1,19 +0,0 @@ -// RUN: llvm-mc -filetype=obj -triple=thumbv7a-none-linux-gnueabi %s -o %t -// RUN: llvm-mc -filetype=obj -triple=thumbv7a-none-linux-gnueabi %S/Inputs/far-arm-thumb-abs.s -o %tfar -// RUN: not ld.lld %t %tfar -o %t2 2>&1 | FileCheck %s -// REQUIRES: arm - .syntax unified - .section .text, "ax",%progbits - .globl _start - .balign 0x10000 - .type _start,%function -_start: - // address of too_far symbols are just out of range of ARM branch with - // 26-bit immediate field and an addend of -8 - bl too_far1 - b too_far2 - beq.w too_far3 - -// CHECK: R_ARM_THM_CALL out of range -// CHECK-NEXT: R_ARM_THM_JUMP24 out of range -// CHECK-NEXT: R_ARM_THM_JUMP19 out of range Index: test/ELF/arm-thumb-branch-rangethunk.s =================================================================== --- /dev/null +++ test/ELF/arm-thumb-branch-rangethunk.s @@ -0,0 +1,36 @@ +// RUN: llvm-mc -filetype=obj -triple=thumbv7a-none-linux-gnueabi %s -o %t +// RUN: llvm-mc -filetype=obj -triple=thumbv7a-none-linux-gnueabi %S/Inputs/far-arm-thumb-abs.s -o %tfar +// RUN: ld.lld %t %tfar -o %t2 2>&1 +// RUN: llvm-objdump -d -triple=thumbv7a-none-linux-gnueabi %t2 +// REQUIRES: arm + .syntax unified + .thumb + .section .text, "ax",%progbits + .globl _start + .balign 0x10000 + .type _start,%function +_start: + // address of too_far symbols are just out of range of ARM branch with + // 26-bit immediate field and an addend of -8 + bl too_far1 + b too_far2 + beq.w too_far3 + +// CHECK: Disassembly of section .text: +// CHECK-NEXT: _start: +// CHECK-NEXT: 20000: 00 f0 04 f8 bl #8 +// CHECK-NEXT: 20004: 00 f0 07 b8 b.w #14 <__Thumbv7ABSLongThunk_too_far2> +// CHECK-NEXT: 20008: 00 f0 0a 80 beq.w #20 <__Thumbv7ABSLongThunk_too_far3> +// CHECK: __Thumbv7ABSLongThunk_too_far1: +// CHECK-NEXT: 2000c: 40 f2 05 0c movw r12, #5 +// CHECK-NEXT: 20010: c0 f2 02 1c movt r12, #258 +// CHECK-NEXT: 20014: 60 47 bx r12 +// CHECK: __Thumbv7ABSLongThunk_too_far2: +// CHECK-NEXT: 20016: 40 f2 09 0c movw r12, #9 +// CHECK-NEXT: 2001a: c0 f2 02 1c movt r12, #258 +// CHECK-NEXT: 2001e: 60 47 bx r12 +// CHECK: __Thumbv7ABSLongThunk_too_far3: +// CHECK-NEXT: 20020: 40 f2 0d 0c movw r12, #13 +// CHECK-NEXT: 20024: c0 f2 12 0c movt r12, #18 +// CHECK-NEXT: 20028: 60 47 bx r12 +// CHECK-NEXT: 2002a: 00 00 movs r0, r0 Index: test/ELF/arm-thumb-condbranch-thunk.s =================================================================== --- /dev/null +++ test/ELF/arm-thumb-condbranch-thunk.s @@ -0,0 +1,117 @@ +// REQUIRES: arm +// RUN: llvm-mc -filetype=obj -triple=thumbv7a-none-linux-gnueabi %s -o %t +// RUN: ld.lld %t -o %t2 2>&1 +// The output file is large, most of it zeroes. We dissassemble only the +// parts we need to speed up the test and avoid a large output file +// RUN: llvm-objdump -d %t2 -start-address=524288 -stop-address=524316 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK1 %s +// RUN: llvm-objdump -d %t2 -start-address=1048576 -stop-address=1048584 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK2 %s +// RUN: llvm-objdump -d %t2 -start-address=1572864 -stop-address=1572872 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK3 %s +// RUN: llvm-objdump -d %t2 -start-address=5242884 -stop-address=5242894 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK4 %s +// RUN: llvm-objdump -d %t2 -start-address=5767168 -stop-address=5767174 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK5 %s +// RUN: llvm-objdump -d %t2 -start-address=16777220 -stop-address=16777240 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK6 %s +// RUN: llvm-objdump -d %t2 -start-address=17825792 -stop-address=17825798 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK7 %s +// Test Range extension Thunks for the Thumb conditional branch instruction. +// This instruction only has a range of 1Mb whereas all the other Thumb wide +// Branch instructions have 16Mb range. We still place our pre-created Thunk +// Sections at 16Mb intervals as conditional branches to a target defined +// in a different section are rare. + .syntax unified +// Define a function aligned on a half megabyte boundary + .macro FUNCTION suff + .section .text.\suff\(), "ax", %progbits + .thumb + .balign 0x80000 + .globl tfunc\suff\() + .type tfunc\suff\(), %function +tfunc\suff\(): + bx lr + .endm + + .globl _start +_start: + FUNCTION 00 +// Long Range Thunk needed for 16Mb range branch, can reach pre-created Thunk +// Section + bl tfunc33 +// CHECK1: Disassembly of section .text: +// CHECK1-NEXT: tfunc00: +// CHECK1-NEXT: 80000: 70 47 bx lr +// CHECK1-NEXT: 80002: 7f f3 ff d7 bl #16252926 +// CHECK1: __Thumbv7ABSLongThunk_tfunc05: +// CHECK1-NEXT: 80008: 40 f2 01 0c movw r12, #1 +// CHECK1-NEXT: 8000c: c0 f2 30 0c movt r12, #48 +// CHECK1-NEXT: 80010: 60 47 bx r12 +// CHECK1: __Thumbv7ABSLongThunk_tfunc00: +// CHECK1-NEXT: 80012: 40 f2 01 0c movw r12, #1 +// CHECK1-NEXT: 80016: c0 f2 08 0c movt r12, #8 +// CHECK1-NEXT: 8001a: 60 47 bx r12 + FUNCTION 01 +// tfunc02 is within range of tfunc02 + beq.w tfunc02 +// tfunc05 is out of range, and we can't reach the pre-created Thunk Section +// create a new one. + bne.w tfunc05 +// CHECK2: tfunc01: +// CHECK2-NEXT: 100000: 70 47 bx lr +// CHECK2-NEXT: 100002: 3f f0 fd a7 beq.w #524282 +// CHECK2-NEXT: 100006: 7f f4 ff a7 bne.w #-524290 <__Thumbv7ABSLongThunk_tfunc05> + FUNCTION 02 +// We can reach the Thunk Section created for bne.w tfunc05 + bne.w tfunc05 + beq.w tfunc00 +// CHECK3: 180000: 70 47 bx lr +// CHECK3-NEXT: 180002: 40 f4 01 80 bne.w #-1048574 <__Thumbv7ABSLongThunk_tfunc05> +// CHECK3-NEXT: 180006: 00 f4 04 80 beq.w #-1048568 <__Thumbv7ABSLongThunk_tfunc00> + FUNCTION 03 + FUNCTION 04 + FUNCTION 05 + FUNCTION 06 + FUNCTION 07 + FUNCTION 08 + FUNCTION 09 +// CHECK4: __Thumbv7ABSLongThunk_tfunc03: +// CHECK4-NEXT: 500004: 40 f2 01 0c movw r12, #1 +// CHECK4-NEXT: 500008: c0 f2 20 0c movt r12, #32 +// CHECK4-NEXT: 50000c: 60 47 bx r12 + FUNCTION 10 +// We can't reach any Thunk Section, create a new one + beq.w tfunc03 +// CHECK5: tfunc10: +// CHECK5-NEXT: 580000: 70 47 bx lr +// CHECK5-NEXT: 580002: 3f f4 ff a7 beq.w #-524290 <__Thumbv7ABSLongThunk_tfunc03> + FUNCTION 11 + FUNCTION 12 + FUNCTION 13 + FUNCTION 14 + FUNCTION 15 + FUNCTION 16 + FUNCTION 17 + FUNCTION 18 + FUNCTION 19 + FUNCTION 20 + FUNCTION 21 + FUNCTION 22 + FUNCTION 23 + FUNCTION 24 + FUNCTION 25 + FUNCTION 26 + FUNCTION 27 + FUNCTION 28 + FUNCTION 29 + FUNCTION 30 + FUNCTION 31 +// CHECK6: __Thumbv7ABSLongThunk_tfunc33: +// CHECK6-NEXT: 1000004: 40 f2 01 0c movw r12, #1 +// CHECK6-NEXT: 1000008: c0 f2 10 1c movt r12, #272 +// CHECK6-NEXT: 100000c: 60 47 bx r12 +// CHECK6: __Thumbv7ABSLongThunk_tfunc00: +// CHECK6-NEXT: 100000e: 40 f2 01 0c movw r12, #1 +// CHECK6-NEXT: 1000012: c0 f2 08 0c movt r12, #8 +// CHECK6-NEXT: 1000016: 60 47 bx r12 + FUNCTION 32 + FUNCTION 33 + // We should be able to reach an existing ThunkSection. + b.w tfunc00 +// CHECK7: tfunc33: +// CHECK7-NEXT: 1100000: 70 47 bx lr +// CHECK7-NEXT: 1100002: 00 f7 04 b8 b.w #-1048568 <__Thumbv7ABSLongThunk_tfunc00> Index: test/ELF/arm-thumb-mix-range-thunk-os.s =================================================================== --- /dev/null +++ test/ELF/arm-thumb-mix-range-thunk-os.s @@ -0,0 +1,195 @@ +// REQUIRES: arm +// RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %s -o %t +// RUN: ld.lld %t -o %t2 2>&1 +// The output file is large, most of it zeroes. We dissassemble only the +// parts we need to speed up the test and avoid a large output file +// RUN: llvm-objdump -d %t2 -start-address=1048576 -stop-address=1048604 -triple=armv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK1 %s +// RUN: llvm-objdump -d %t2 -start-address=2097152 -stop-address=2097162 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK2 %s +// RUN: llvm-objdump -d %t2 -start-address=16777220 -stop-address=16777232 -triple=armv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK3 %s +// RUN: llvm-objdump -d %t2 -start-address=16777232 -stop-address=16777242 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK4 %s +// RUN: llvm-objdump -d %t2 -start-address=32505860 -stop-address=32505870 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK5 %s +// RUN: llvm-objdump -d %t2 -start-address=35651584 -stop-address=35651590 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK6 %s +// RUN: llvm-objdump -d %t2 -start-address=36700160 -stop-address=36700168 -triple=armv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK7 %s +// RUN: llvm-objdump -d %t2 -start-address=48234500 -stop-address=48234512 -triple=armv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK8 %s +// RUN: llvm-objdump -d %t2 -start-address=63963140 -stop-address=63963160 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK9 %s +// RUN: llvm-objdump -d %t2 -start-address=68157440 -stop-address=68157452 -triple=armv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK10 %s +// RUN: llvm-objdump -d %t2 -start-address=69206016 -stop-address=69206024 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK11 %s + +// Test the Range extension Thunks for ARM and Thumb when all the code is in a +// single OutputSection. The ARM branches and branch and link instructions +// have a range of 32Mb, the Thumb unconditional branch and +// branch and link instructions have . We create a series of Functions a +// megabyte apart. We expect range extension thunks to be created when a +// branch is out of range. Thunks will be reused whenever they are in range + .syntax unified + +// Define a function aligned on a megabyte boundary + .macro ARMFUNCTION suff + .section .text.\suff\(), "ax", %progbits + .arm + .balign 0x100000 + .globl afunc\suff\() + .type afunc\suff\(), %function +afunc\suff\(): + bx lr + .endm + +// Define a function aligned on a megabyte boundary + .macro THUMBFUNCTION suff + .section .text.\suff\(), "ax", %progbits + .thumb + .balign 0x100000 + .globl tfunc\suff\() + .type tfunc\suff\(), %function +tfunc\suff\(): + bx lr + .endm + + .section .text, "ax", %progbits + .thumb + .globl _start +_start: + + ARMFUNCTION 00 +// Expect ARM bl to be in range (can use blx to change state) + bl tfunc31 +// ARM b and beq are in range but need Thunk to change state to Thumb + b tfunc31 + beq tfunc31 +// afunc32 is out of range of ARM branch and branch and link + bl afunc32 + b afunc32 + bne afunc32 +// CHECK1: afunc00: +// CHECK1-NEXT: 100000: 1e ff 2f e1 bx lr +// CHECK1-NEXT: 100004: fd ff 7b fa blx #32505844 +// CHECK1-NEXT: 100008: fd ff 3b ea b #15728628 +// CHECK1-NEXT: 10000c: fc ff 3b 0a beq #15728624 +// CHECK1-NEXT: 100010: fa ff 7f eb bl #33554408 +// CHECK1-NEXT: 100014: f9 ff 7f ea b #33554404 +// CHECK1-NEXT: 100018: f8 ff 7f 1a bne #33554400 + THUMBFUNCTION 01 +// Expect Thumb bl to be in range (can use blx to change state) + bl afunc14 +// In range but need thunk to change state to Thumb + b.w afunc14 +// CHECK2: tfunc01: +// CHECK2-NEXT: 200000: 70 47 bx lr +// CHECK2-NEXT: 200002: ff f0 fe c7 blx #13631484 +// CHECK2-NEXT: 200006: 00 f2 03 90 b.w #14680070 <__Thumbv7ABSLongThunk_afunc14> + + ARMFUNCTION 02 + THUMBFUNCTION 03 + ARMFUNCTION 04 + THUMBFUNCTION 05 + ARMFUNCTION 06 + THUMBFUNCTION 07 + ARMFUNCTION 08 + THUMBFUNCTION 09 + ARMFUNCTION 10 + THUMBFUNCTION 11 + ARMFUNCTION 12 + THUMBFUNCTION 13 + ARMFUNCTION 14 +// CHECK3: __ARMv7ABSLongThunk_tfunc31: +// CHECK3-NEXT: 1000004: 01 c0 00 e3 movw r12, #1 +// CHECK3-NEXT: 1000008: 00 c2 40 e3 movt r12, #512 +// CHECK3-NEXT: 100000c: 1c ff 2f e1 bx r12 +// CHECK4: __Thumbv7ABSLongThunk_afunc14: +// CHECK4-NEXT: 1000010: 40 f2 00 0c movw r12, #0 +// CHECK4-NEXT: 1000014: c0 f2 f0 0c movt r12, #240 +// CHECK4-NEXT: 1000018: 60 47 bx r12 + THUMBFUNCTION 15 + ARMFUNCTION 16 + THUMBFUNCTION 17 + ARMFUNCTION 18 + THUMBFUNCTION 19 + ARMFUNCTION 20 + THUMBFUNCTION 21 + ARMFUNCTION 22 + THUMBFUNCTION 23 + ARMFUNCTION 24 + THUMBFUNCTION 25 + ARMFUNCTION 26 + THUMBFUNCTION 27 + ARMFUNCTION 28 + THUMBFUNCTION 29 + ARMFUNCTION 30 +// Expect precreated Thunk Section here +// CHECK5: __Thumbv7ABSLongThunk_afunc00: +// CHECK5-NEXT: 1f00004: 40 f2 00 0c movw r12, #0 +// CHECK5-NEXT: 1f00008: c0 f2 10 0c movt r12, #16 +// CHECK5-NEXT: 1f0000c: 60 47 bx r12 + THUMBFUNCTION 31 + ARMFUNCTION 32 + THUMBFUNCTION 33 +// Out of range, can only reach closest Thunk Section + bl afunc00 +// CHECK6: tfunc33: +// CHECK6-NEXT: 2200000: 70 47 bx lr +// CHECK6-NEXT: 2200002: ff f4 ff ff bl #-3145730 + ARMFUNCTION 34 +// Out of range, can reach earlier Thunk Section +// CHECK7: afunc34: +// CHECK7-NEXT: 2300000: 1e ff 2f e1 bx lr +// CHECK7-NEXT: 2300004: fe ff ef fa blx #-4194312 <__Thumbv7ABSLongThunk_afunc00 + bl afunc00 + THUMBFUNCTION 35 + ARMFUNCTION 36 + THUMBFUNCTION 37 + ARMFUNCTION 38 + THUMBFUNCTION 39 + ARMFUNCTION 40 + THUMBFUNCTION 41 + ARMFUNCTION 42 + THUMBFUNCTION 43 + ARMFUNCTION 44 + THUMBFUNCTION 45 +// Expect precreated Thunk Section here +// CHECK8: __ARMv7ABSLongThunk_tfunc35: +// CHECK8-NEXT: 2e00004: 01 c0 00 e3 movw r12, #1 +// CHECK8-NEXT: 2e00008: 40 c2 40 e3 movt r12, #576 +// CHECK8-NEXT: 2e0000c: 1c ff 2f e1 bx r12 + ARMFUNCTION 46 + THUMBFUNCTION 47 + ARMFUNCTION 48 + THUMBFUNCTION 49 + ARMFUNCTION 50 + THUMBFUNCTION 51 + ARMFUNCTION 52 + THUMBFUNCTION 53 + ARMFUNCTION 54 + THUMBFUNCTION 55 + ARMFUNCTION 56 + THUMBFUNCTION 57 + ARMFUNCTION 58 + THUMBFUNCTION 59 + ARMFUNCTION 60 +// Expect precreated Thunk Section here +// CHECK9: __Thumbv7ABSLongThunk_afunc34: +// CHECK9-NEXT: 3d00004: 40 f2 00 0c movw r12, #0 +// CHECK9-NEXT: 3d00008: c0 f2 30 2c movt r12, #560 +// CHECK9-NEXT: 3d0000c: 60 47 bx r12 +// CHECK9: __Thumbv7ABSLongThunk_tfunc35: +// CHECK9-NEXT: 3d0000e: 40 f2 01 0c movw r12, #1 +// CHECK9-NEXT: 3d00012: c0 f2 40 2c movt r12, #576 +// CHECK9-NEXT: 3d00016: 60 47 bx r12 + THUMBFUNCTION 61 + ARMFUNCTION 62 + THUMBFUNCTION 63 + ARMFUNCTION 64 +// afunc34 is in range, as is tfunc35 but a branch needs a state change Thunk + bl afunc34 + b tfunc35 +// CHECK10: afunc64: +// CHECK10-NEXT: 4100000: 1e ff 2f e1 bx lr +// CHECK10-NEXT: 4100004: fd ff 87 eb bl #-31457292 +// CHECK10-NEXT: 4100008: fd ff b3 ea b #-19922956 <__ARMv7ABSLongThunk_tfunc35> + THUMBFUNCTION 65 +// afunc34 and tfunc35 are both out of range + bl afunc34 + bl tfunc35 +// CHECK11: tfunc65: +// CHECK11: 4200000: 70 47 bx lr +// CHECK11-NEXT: 4200002: ff f6 ff f7 bl #-5242882 +// CHECK11-NEXT: 4200006: 00 f7 02 f0 bl #-5242876 Index: test/ELF/arm-thumb-plt-range-thunk-os.s =================================================================== --- /dev/null +++ test/ELF/arm-thumb-plt-range-thunk-os.s @@ -0,0 +1,88 @@ +// REQUIRES: arm +// RUN: llvm-mc -filetype=obj -triple=thumbv7a-none-linux-gnueabi %s -o %t +// RUN: ld.lld %t --shared -o %t.so +// The output file is large, most of it zeroes. We dissassemble only the +// parts we need to speed up the test and avoid a large output file +// RUN: llvm-objdump -d %t.so -start-address=8388608 -stop-address=8388624 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK1 %s +// RUN: llvm-objdump -d %t.so -start-address=16777216 -stop-address=16777256 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK2 %s +// RUN: llvm-objdump -d %t.so -start-address=25165824 -stop-address=25165828 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK3 %s +// RUN: llvm-objdump -d %t.so -start-address=25165828 -stop-address=25165908 -triple=armv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK4 %s + .syntax unified + .thumb + +// Make sure that we generate a range extension thunk to a PLT entry + .section ".text.1", "ax", %progbits + .global sym1 + .global elsewhere + .type elsewhere, %function + .global preemptible + .type preemptible, %function + .global far_preemptible + .type far_preemptible, %function +sym1: + bl elsewhere + bl preemptible + bx lr +preemptible: + bl far_preemptible + bx lr +// CHECK1: Disassembly of section .text: +// CHECK1-NEXT: sym1: +// CHECK1-NEXT: 800000: 00 f0 00 d8 bl #8388608 +// CHECK1-NEXT: 800004: 00 f0 04 d8 bl #8388616 +// CHECK1-NEXT: 800008: 70 47 bx lr +// CHECK1: preemptible: +// CHECK1-NEXT: 80000a: 00 f0 07 d8 bl #8388622 +// CHECK1-NEXT: 80000e: 70 47 bx lr + + .section .text.2, "ax", %progbits + .balign 0x0800000 + bx lr +// CHECK2: __ThumbV7PILongThunk_elsewhere: +// CHECK2-NEXT: 1000004: 40 f2 14 0c movw r12, #20 +// CHECK2-NEXT: 1000008: c0 f2 80 0c movt r12, #128 +// CHECK2-NEXT: 100000c: fc 44 add r12, pc +// CHECK2-NEXT: 100000e: 60 47 bx r12 +// CHECK2: __ThumbV7PILongThunk_preemptible: +// CHECK2-NEXT: 1000010: 40 f2 18 0c movw r12, #24 +// CHECK2-NEXT: 1000014: c0 f2 80 0c movt r12, #128 +// CHECK2-NEXT: 1000018: fc 44 add r12, pc +// CHECK2-NEXT: 100001a: 60 47 bx r12 +// CHECK2: __ThumbV7PILongThunk_far_preemptible: +// CHECK2-NEXT: 100001c: 40 f2 1c 0c movw r12, #28 +// CHECK2-NEXT: 1000020: c0 f2 80 0c movt r12, #128 +// CHECK2-NEXT: 1000024: fc 44 add r12, pc +// CHECK2-NEXT: 1000026: 60 47 bx r12 + .section .text.3, "ax", %progbits +.balign 0x0800000 +far_preemptible: + bl elsewhere +// CHECK3: far_preemptible: +// CHECK3: 1800000: 00 f0 10 e8 blx #32 + +// CHECK4: Disassembly of section .plt: +// CHECK4-NEXT: $a: +// CHECK4-NEXT: 1800010: 04 e0 2d e5 str lr, [sp, #-4]! +// CHECK4-NEXT: 1800014: 04 e0 9f e5 ldr lr, [pc, #4] +// CHECK4-NEXT: 1800018: 0e e0 8f e0 add lr, pc, lr +// CHECK4-NEXT: 180001c: 08 f0 be e5 ldr pc, [lr, #8]! +// CHECK4: $d: +// CHECK4-NEXT: 1800020: e0 0f 00 00 .word 0x00000fe0 +// CHECK4: $a: +// CHECK4-NEXT: 1800024: 04 c0 9f e5 ldr r12, [pc, #4] +// CHECK4-NEXT: 1800028: 0f c0 8c e0 add r12, r12, pc +// CHECK4-NEXT: 180002c: 00 f0 9c e5 ldr pc, [r12] +// CHECK4: $d: +// CHECK4-NEXT: 1800030: dc 0f 00 00 .word 0x00000fdc +// CHECK4: $a: +// CHECK4-NEXT: 1800034: 04 c0 9f e5 ldr r12, [pc, #4] +// CHECK4-NEXT: 1800038: 0f c0 8c e0 add r12, r12, pc +// CHECK4-NEXT: 180003c: 00 f0 9c e5 ldr pc, [r12] +// CHECK4: $d: +// CHECK4-NEXT: 1800040: d0 0f 00 00 .word 0x00000fd0 +// CHECK4: $a: +// CHECK4-NEXT: 1800044: 04 c0 9f e5 ldr r12, [pc, #4] +// CHECK4-NEXT: 1800048: 0f c0 8c e0 add r12, r12, pc +// CHECK4-NEXT: 180004c: 00 f0 9c e5 ldr pc, [r12] +// CHECK4: $d: +// CHECK4-NEXT: 1800050: c4 0f 00 00 .word 0x00000fc4 Index: test/ELF/arm-thumb-range-thunk-os.s =================================================================== --- /dev/null +++ test/ELF/arm-thumb-range-thunk-os.s @@ -0,0 +1,159 @@ +// REQUIRES: arm +// RUN: llvm-mc -filetype=obj -triple=thumbv7a-none-linux-gnueabi %s -o %t +// RUN: ld.lld %t -o %t2 2>&1 +// The output file is large, most of it zeroes. We dissassemble only the +// parts we need to speed up the test and avoid a large output file +// RUN: llvm-objdump -d %t2 -start-address=1048576 -stop-address=1048588 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK1 %s +// RUN: llvm-objdump -d %t2 -start-address=2097152 -stop-address=2097154 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK2 %s +// RUN: llvm-objdump -d %t2 -start-address=3145728 -stop-address=3145730 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK3 %s +// RUN: llvm-objdump -d %t2 -start-address=4194304 -stop-address=4194310 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK4 %s +// RUN: llvm-objdump -d %t2 -start-address=16777216 -stop-address=16777270 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK5 %s +// RUN: llvm-objdump -d %t2 -start-address=17825792 -stop-address=17825808 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK6 %s +// RUN: llvm-objdump -d %t2 -start-address=31457280 -stop-address=31457286 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK7 %s +// RUN: llvm-objdump -d %t2 -start-address=32505860 -stop-address=32505880 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK8 %s +// RUN: llvm-objdump -d %t2 -start-address=35651584 -stop-address=35651594 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK9 %s +// RUN: llvm-objdump -d %t2 -start-address=36700160 -stop-address=36700170 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK10 %s + +// Test the Range extension Thunks for Thumb when all the code is in a single +// OutputSection. The Thumb unconditional branch b.w and branch and link bl +// instructions have a range of 16Mb. We create a series of Functions a +// megabyte apart. We expect range extension thunks to be created when a +// branch is out of range. Thunks will be reused whenever they are in range + .syntax unified + +// Define a function aligned on a megabyte boundary + .macro FUNCTION suff + .section .text.\suff\(), "ax", %progbits + .thumb + .balign 0x100000 + .globl tfunc\suff\() + .type tfunc\suff\(), %function +tfunc\suff\(): + bx lr + .endm + + .section .text, "ax", %progbits + .thumb + .globl _start +_start: +// tfunc00 and tfunc15 are within 16Mb no Range Thunks expected + bl tfunc00 + bl tfunc15 +// tfunc16 is > 16Mb away, expect a Range Thunk to be generated, to go into +// the first of the pre-created ThunkSections. + bl tfunc16 +// CHECK1: Disassembly of section .text: +// CHECK1-NEXT: _start: +// CHECK1-NEXT: 100000: ff f0 fe ff bl #1048572 +// CHECK1-NEXT: 100004: ff f3 fc d7 bl #16777208 +// CHECK1-NEXT: 100008: ff f2 fc d7 bl #15728632 + + FUNCTION 00 +// CHECK2: tfunc00: +// CHECK2-NEXT: 200000: 70 47 bx lr + FUNCTION 01 +// CHECK3: tfunc01: +// CHECK3-NEXT: 300000: 70 47 bx lr + FUNCTION 02 +// tfunc28 is > 16Mb away, expect a Range Thunk to be generated, to go into +// the first of the pre-created ThunkSections. + b.w tfunc28 +// CHECK4: tfunc02: +// CHECK4-NEXT: 400000: 70 47 bx lr +// CHECK4-NEXT: 400002: 00 f0 04 90 b.w #12582920 <__Thumbv7ABSLongThunk_tfunc28> + FUNCTION 03 + FUNCTION 04 + FUNCTION 05 + FUNCTION 06 + FUNCTION 07 + FUNCTION 08 + FUNCTION 09 + FUNCTION 10 + FUNCTION 11 + FUNCTION 12 + FUNCTION 13 + FUNCTION 14 +// Expect precreated ThunkSection here +// CHECK5: __Thumbv7ABSLongThunk_tfunc16: +// CHECK5-NEXT: 1000004: 40 f2 01 0c movw r12, #1 +// CHECK5-NEXT: 1000008: c0 f2 20 1c movt r12, #288 +// CHECK5-NEXT: 100000c: 60 47 bx r12 +// CHECK5: __Thumbv7ABSLongThunk_tfunc28: +// CHECK5-NEXT: 100000e: 40 f2 01 0c movw r12, #1 +// CHECK5-NEXT: 1000012: c0 f2 e0 1c movt r12, #480 +// CHECK5-NEXT: 1000016: 60 47 bx r12 +// CHECK5: __Thumbv7ABSLongThunk_tfunc32: +// CHECK5-NEXT: 1000018: 40 f2 01 0c movw r12, #1 +// CHECK5-NEXT: 100001c: c0 f2 20 2c movt r12, #544 +// CHECK5-NEXT: 1000020: 60 47 bx r12 +// CHECK5: __Thumbv7ABSLongThunk_tfunc33: +// CHECK5-NEXT: 1000022: 40 f2 01 0c movw r12, #1 +// CHECK5-NEXT: 1000026: c0 f2 30 2c movt r12, #560 +// CHECK5-NEXT: 100002a: 60 47 bx r12 +// CHECK5: __Thumbv7ABSLongThunk_tfunc02: +// CHECK5-NEXT: 100002c: 40 f2 01 0c movw r12, #1 +// CHECK5-NEXT: 1000030: c0 f2 40 0c movt r12, #64 +// CHECK5-NEXT: 1000034: 60 47 bx r12 + FUNCTION 15 +// tfunc00 and tfunc01 are < 16Mb away, expect no range extension thunks + bl tfunc00 + bl tfunc01 +// tfunc32 and tfunc33 are > 16Mb away, expect range extension thunks in the +// precreated thunk section + bl tfunc32 + bl tfunc33 +// CHECK6: tfunc15: +// CHECK6-NEXT: 1100000: 70 47 bx lr +// CHECK6-NEXT: 1100002: ff f4 fd d7 bl #-15728646 +// CHECK6-NEXT: 1100006: ff f5 fb d7 bl #-14680074 +// CHECK6-NEXT: 110000a: 00 f7 05 f8 bl #-1048566 +// CHECK6-NEXT: 110000e: 00 f7 08 f8 bl #-1048560 + FUNCTION 16 + FUNCTION 17 + FUNCTION 18 + FUNCTION 19 + FUNCTION 20 + FUNCTION 21 + FUNCTION 22 + FUNCTION 23 + FUNCTION 24 + FUNCTION 25 + FUNCTION 26 + FUNCTION 27 + FUNCTION 28 +// tfunc02 is > 16Mb away, expect range extension thunks in precreated thunk +// section +// CHECK7: tfunc28: +// CHECK7-NEXT: 1e00000: 70 47 bx lr +// CHECK7-NEXT: 1e00002: 00 f6 13 90 b.w #-14680026 <__Thumbv7ABSLongThunk_tfunc02> + + b.w tfunc02 + FUNCTION 29 +// Expect another precreated thunk section here +// CHECK8: __Thumbv7ABSLongThunk_tfunc15: +// CHECK8-NEXT: 1f00004: 40 f2 01 0c movw r12, #1 +// CHECK8-NEXT: 1f00008: c0 f2 10 1c movt r12, #272 +// CHECK8-NEXT: 1f0000c: 60 47 bx r12 +// CHECK8: __Thumbv7ABSLongThunk_tfunc16: +// CHECK8-NEXT: 1f0000e: 40 f2 01 0c movw r12, #1 +// CHECK8-NEXT: 1f00012: c0 f2 20 1c movt r12, #288 +// CHECK8-NEXT: 1f00016: 60 47 bx r12 + FUNCTION 30 + FUNCTION 31 + FUNCTION 32 + // tfunc15 and tfunc16 are > 16 Mb away expect Thunks in the nearest + // precreated thunk section. + bl tfunc15 + bl tfunc16 +// CHECK9: tfunc32: +// CHECK9: 2200000: 70 47 bx lr +// CHECK9-NEXT: 2200002: ff f4 ff ff bl #-3145730 +// CHECK9-NEXT: 2200006: 00 f5 02 f8 bl #-3145724 + + FUNCTION 33 + bl tfunc15 + bl tfunc16 +// CHECK10: tfunc33: +// CHECK10: 2300000: 70 47 bx lr +// CHECK10-NEXT: 2300002: ff f7 ff f7 bl #-4194306 +// CHECK10-NEXT: 2300006: 00 f4 02 f8 bl #-4194300 Index: test/ELF/arm-thumb-thunk-symbols.s =================================================================== --- test/ELF/arm-thumb-thunk-symbols.s +++ test/ELF/arm-thumb-thunk-symbols.s @@ -25,18 +25,18 @@ b thumb_fn // CHECK: Name: __Thumbv7ABSLongThunk_arm_fn -// CHECK-NEXT: Value: 0x11005 +// CHECK-NEXT: Value: 0x12005 // CHECK-NEXT: Size: 10 // CHECK-NEXT: Binding: Local (0x0) // CHECK-NEXT: Type: Function (0x2) // CHECK: Name: __ARMv7ABSLongThunk_thumb_fn -// CHECK-NEXT: Value: 0x11010 +// CHECK-NEXT: Value: 0x12010 // CHECK-NEXT: Size: 12 // CHECK-NEXT: Binding: Local (0x0) // CHECK-NEXT: Type: Function (0x2) // CHECK-PI: Name: __ThumbV7PILongThunk_arm_fn -// CHECK-PI-NEXT: Value: 0x1005 +// CHECK-PI-NEXT: Value: 0x2005 // CHECK-PI-NEXT: Size: 12 // CHECK-PI-NEXT: Binding: Local (0x0) // CHECK-PI-NEXT: Type: Function (0x2) Index: test/ELF/arm-thunk-linkerscript-dotexpr.s =================================================================== --- /dev/null +++ test/ELF/arm-thunk-linkerscript-dotexpr.s @@ -0,0 +1,76 @@ +// RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %s -o %t +// RUN: echo "SECTIONS { \ +// RUN: . = SIZEOF_HEADERS; \ +// RUN: .text_low : { *(.text_low) *(.text_low2) . = . + 0x2000000 ; *(.text_high) *(.text_high2) } \ +// RUN: } " > %t.script +// RUN: ld.lld --script %t.script %t -o %t2 2>&1 +// RUN: llvm-objdump -d %t2 -start-address=148 -stop-address=188 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK1 %s +// RUN: llvm-objdump -d %t2 -start-address=33554620 -stop-address=33554654 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK2 %s +// Test that range extension thunks can handle location expressions within +// a Section Description + .syntax unified + .section .text_low, "ax", %progbits + .thumb + .globl _start +_start: bx lr + .globl low_target + .type low_target, %function +low_target: + bl high_target + bl high_target2 + + .section .text_low2, "ax", %progbits + .thumb + .globl low_target2 + .type low_target2, %function +low_target2: + bl high_target + bl high_target2 +// CHECK1: Disassembly of section .text_low: +// CHECK1-NEXT: _start: +// CHECK1-NEXT: 94: 70 47 bx lr +// CHECK1: low_target: +// CHECK1-NEXT: 96: 00 f0 03 f8 bl #6 +// CHECK1-NEXT: 9a: 00 f0 06 f8 bl #12 +// CHECK1: __Thumbv7ABSLongThunk_high_target: +// CHECK1-NEXT: a0: 40 f2 bd 0c movw r12, #189 +// CHECK1-NEXT: a4: c0 f2 00 2c movt r12, #512 +// CHECK1-NEXT: a8: 60 47 bx r12 +// CHECK1: __Thumbv7ABSLongThunk_high_target2: +// CHECK1-NEXT: aa: 40 f2 d9 0c movw r12, #217 +// CHECK1-NEXT: ae: c0 f2 00 2c movt r12, #512 +// CHECK1-NEXT: b2: 60 47 bx r12 +// CHECK1: low_target2: +// CHECK1-NEXT: b4: ff f7 f4 ff bl #-24 +// CHECK1-NEXT: b8: ff f7 f7 ff bl #-18 + + .section .text_high, "ax", %progbits + .thumb + .globl high_target + .type high_target, %function +high_target: + bl low_target + bl low_target2 + + .section .text_high2, "ax", %progbits + .thumb + .globl high_target2 + .type high_target2, %function +high_target2: + bl low_target + bl low_target2 + +// CHECK2: high_target: +// CHECK2-NEXT: 20000bc: 00 f0 02 f8 bl #4 +// CHECK2-NEXT: 20000c0: 00 f0 05 f8 bl #10 +// CHECK2: __Thumbv7ABSLongThunk_low_target: +// CHECK2-NEXT: 20000c4: 40 f2 97 0c movw r12, #151 +// CHECK2-NEXT: 20000c8: c0 f2 00 0c movt r12, #0 +// CHECK2-NEXT: 20000cc: 60 47 bx r12 +// CHECK2: __Thumbv7ABSLongThunk_low_target2: +// CHECK2-NEXT: 20000ce: 40 f2 b5 0c movw r12, #181 +// CHECK2-NEXT: 20000d2: c0 f2 00 0c movt r12, #0 +// CHECK2-NEXT: 20000d6: 60 47 bx r12 +// CHECK2: high_target2: +// CHECK2-NEXT: 20000d8: ff f7 f4 ff bl #-24 +// CHECK2-NEXT: 20000dc: ff f7 f7 ff bl #-18 Index: test/ELF/arm-thunk-linkerscript-large.s =================================================================== --- /dev/null +++ test/ELF/arm-thunk-linkerscript-large.s @@ -0,0 +1,176 @@ +// REQUIRES: arm +// RUN: llvm-mc -filetype=obj -triple=thumbv7a-none-linux-gnueabi %s -o %t +// RUN: echo "SECTIONS { \ +// RUN: .text 0x100000 : { *(.text) } \ +// RUN: .textl : { *(.text_l0*) *(.text_l1*) *(.text_l2*) *(.text_l3*) } \ +// RUN: .texth : { *(.text_h0*) *(.text_h1*) *(.text_h2*) *(.text_h3*) } \ +// RUN: }" > %t.script +// RUN: ld.lld --script %t.script %t -o %t2 2>&1 +// The output file is large, most of it zeroes. We dissassemble only the +// parts we need to speed up the test and avoid a large output file +// RUN: llvm-objdump -d %t2 -start-address=1048576 -stop-address=1048594 -triple=thumbv7a-linux-gnueabihf | FileCheck --check-prefix=CHECK1 %s +// RUN: llvm-objdump -d %t2 -start-address=2097152 -stop-address=2097160 -triple=thumbv7a-linux-gnueabihf | FileCheck --check-prefix=CHECK2 %s +// RUN: llvm-objdump -d %t2 -start-address=11534340 -stop-address=11534350 -triple=thumbv7a-linux-gnueabihf | FileCheck --check-prefix=CHECK3 %s +// RUN: llvm-objdump -d %t2 -start-address=34603008 -stop-address=34603034 -triple=thumbv7a-linux-gnueabihf | FileCheck --check-prefix=CHECK4 %s +// RUN: llvm-objdump -d %t2 -start-address=35651584 -stop-address=35651598 -triple=thumbv7a-linux-gnueabihf | FileCheck --check-prefix=CHECK5 %s +// RUN: llvm-objdump -d %t2 -start-address=68157440 -stop-address=68157472 -triple=thumbv7a-linux-gnueabihf | FileCheck --check-prefix=CHECK6 %s + +// Test the range extensions in a linker script where there are several +// OutputSections requiring range extension Thunks. We should be able to reuse +// Thunks between OutputSections but our placement of new Thunks is done on a +// per OutputSection basis + .syntax unified + +// Define a function that we can match with .text_l* aligned on a megabyte // boundary + .macro FUNCTIONL suff + .section .text_l\suff\(), "ax", %progbits + .thumb + .balign 0x100000 + .globl tfuncl\suff\() + .type tfuncl\suff\(), %function +tfuncl\suff\(): + bx lr + .endm + +// Define a function that we can match with .text_h* aligned on a megabyte +// boundary + .macro FUNCTIONH suff + .section .text_h\suff\(), "ax", %progbits + .thumb + .balign 0x100000 + .globl tfunch\suff\() + .type tfunch\suff\(), %function +tfunch\suff\(): + bx lr + .endm + + .section .text, "ax", %progbits + .thumb + .globl _start +_start: + bl tfuncl00 + // Expect a range extension thunk in .text OutputSection + bl tfunch31 +// CHECK1: Disassembly of section .text: +// CHECK1-NEXT: _start: +// CHECK1-NEXT: 100000: ff f0 fe ff bl #1048572 +// CHECK1-NEXT: 100004: 00 f0 00 f8 bl #0 +// CHECK1: __Thumbv7ABSLongThunk_tfunch31: +// CHECK1-NEXT: 100008: 40 f2 01 0c movw r12, #1 +// CHECK1-NEXT: 10000c: c0 f2 10 4c movt r12, #1040 +// CHECK1-NEXT: 100010: 60 47 bx r12 + FUNCTIONL 00 + // Create a range extension thunk in .textl + bl tfuncl24 + // We can reuse existing thunk in .text + bl tfunch31 +// CHECK2: Disassembly of section .textl: +// CHECK2-NEXT: tfuncl00: +// CHECK2-NEXT: 200000: 70 47 bx lr +// CHECK2-NEXT: 200002: ff f0 ff df bl #9437182 +// CHECK2-NEXT: 200006: ff f6 ff ff bl #-1048578 + FUNCTIONL 01 + FUNCTIONL 02 + FUNCTIONL 03 + FUNCTIONL 04 + FUNCTIONL 05 + FUNCTIONL 06 + FUNCTIONL 07 + FUNCTIONL 08 + FUNCTIONL 09 +// CHECK3: __Thumbv7ABSLongThunk_tfuncl24: +// CHECK3-NEXT: b00004: 40 f2 01 0c movw r12, #1 +// CHECK3-NEXT: b00008: c0 f2 a0 1c movt r12, #416 +// CHECK3-NEXT: b0000c: 60 47 bx r12 + FUNCTIONL 10 + FUNCTIONL 11 + FUNCTIONL 12 + FUNCTIONL 13 + FUNCTIONL 14 + FUNCTIONL 15 + FUNCTIONL 16 + FUNCTIONL 17 + FUNCTIONL 18 + FUNCTIONL 19 + FUNCTIONL 20 + FUNCTIONL 21 + FUNCTIONL 22 + FUNCTIONL 23 + FUNCTIONL 24 + FUNCTIONL 25 + FUNCTIONL 26 + FUNCTIONL 27 + FUNCTIONL 28 + FUNCTIONL 29 + FUNCTIONL 30 + FUNCTIONL 31 + // Create range extension thunks in .textl + bl tfuncl00 + bl tfuncl24 + // Shouldn't need a thunk + bl tfunch00 +// CHECK4: 2100002: 00 f0 05 f8 bl #10 +// CHECK4-NEXT: 2100006: ff f4 fb f7 bl #-7340042 +// CHECK4-NEXT: 210000a: ff f0 f9 ff bl #1048562 +// CHECK4: __Thumbv7ABSLongThunk_tfuncl00: +// CHECK4-NEXT: 2100010: 40 f2 01 0c movw r12, #1 +// CHECK4-NEXT: 2100014: c0 f2 20 0c movt r12, #32 +// CHECK4-NEXT: 2100018: 60 47 bx r12 + FUNCTIONH 00 + // Can reuse existing thunks in .textl + bl tfuncl00 + bl tfuncl24 + // Shouldn't need a thunk + bl tfuncl31 +// CHECK5: Disassembly of section .texth: +// CHECK5-NEXT: tfunch00: +// CHECK5-NEXT: 2200000: 70 47 bx lr +// CHECK5-NEXT: 2200002: 00 f7 05 f8 bl #-1048566 +// CHECK5-NEXT: 2200006: ff f7 fb df bl #-8388618 +// CHECK5-NEXT: 220000a: ff f6 f9 ff bl #-1048590 + FUNCTIONH 01 + FUNCTIONH 02 + FUNCTIONH 03 + FUNCTIONH 04 + FUNCTIONH 05 + FUNCTIONH 06 + FUNCTIONH 07 + FUNCTIONH 08 + FUNCTIONH 09 + FUNCTIONH 10 + FUNCTIONH 11 + FUNCTIONH 12 + FUNCTIONH 13 + FUNCTIONH 14 + FUNCTIONH 15 + FUNCTIONH 16 + FUNCTIONH 17 + FUNCTIONH 18 + FUNCTIONH 19 + FUNCTIONH 20 + FUNCTIONH 21 + FUNCTIONH 22 + FUNCTIONH 23 + FUNCTIONH 24 + FUNCTIONH 25 + FUNCTIONH 26 + FUNCTIONH 27 + FUNCTIONH 28 + FUNCTIONH 29 + FUNCTIONH 30 + FUNCTIONH 31 +// expect Thunks in .texth + bl tfuncl00 + bl tfunch00 +// CHECK6: tfunch31: +// CHECK6-NEXT: 4100000: 70 47 bx lr +// CHECK6-NEXT: 4100002: 00 f0 03 f8 bl #6 +// CHECK6-NEXT: 4100006: 00 f0 06 f8 bl #12 +// CHECK6: __Thumbv7ABSLongThunk_tfuncl00: +// CHECK6-NEXT: 410000c: 40 f2 01 0c movw r12, #1 +// CHECK6-NEXT: 4100010: c0 f2 20 0c movt r12, #32 +// CHECK6-NEXT: 4100014: 60 47 bx r12 +// CHECK6: __Thumbv7ABSLongThunk_tfunch00: +// CHECK6-NEXT: 4100016: 40 f2 01 0c movw r12, #1 +// CHECK6-NEXT: 410001a: c0 f2 20 2c movt r12, #544 +// CHECK6-NEXT: 410001e: 60 47 bx r12 Index: test/ELF/arm-thunk-linkerscript-orphan.s =================================================================== --- /dev/null +++ test/ELF/arm-thunk-linkerscript-orphan.s @@ -0,0 +1,63 @@ +// REQUIRES: arm +// RUN: llvm-mc -filetype=obj -triple=thumbv7a-none-linux-gnueabi %s -o %t +// RUN: echo "SECTIONS { \ +// RUN: .text_low 0x100000 : { *(.text_low) } \ +// RUN: .text_high 0x2000000 : { *(.text_high) } \ +// RUN: .data : { *(.data) } \ +// RUN: }" > %t.script +// RUN: ld.lld --script %t.script %t -o %t2 2>&1 +// RUN: llvm-objdump -d -triple=thumbv7a-none-linux-gnueabi %t2 | FileCheck %s + .syntax unified + .section .text_low, "ax", %progbits + .thumb + .globl _start +_start: bx lr + .globl low_target + .type low_target, %function +low_target: + bl high_target + bl orphan_target +// CHECK: Disassembly of section .text_low: +// CHECK-NEXT: _start: +// CHECK-NEXT: 100000: 70 47 bx lr +// CHECK: low_target: +// CHECK-NEXT: 100002: 00 f0 03 f8 bl #6 +// CHECK-NEXT: 100006: 00 f0 06 f8 bl #12 +// CHECK: __Thumbv7ABSLongThunk_high_target: +// CHECK-NEXT: 10000c: 40 f2 01 0c movw r12, #1 +// CHECK-NEXT: 100010: c0 f2 00 2c movt r12, #512 +// CHECK-NEXT: 100014: 60 47 bx r12 +// CHECK: __Thumbv7ABSLongThunk_orphan_target: +// CHECK-NEXT: 100016: 40 f2 15 0c movw r12, #21 +// CHECK-NEXT: 10001a: c0 f2 00 2c movt r12, #512 +// CHECK-NEXT: 10001e: 60 47 bx r12 + .section .text_high, "ax", %progbits + .thumb + .globl high_target + .type high_target, %function +high_target: + bl low_target + bl orphan_target +// CHECK: Disassembly of section .text_high: +// CHECK-NEXT: high_target: +// CHECK-NEXT: 2000000: 00 f0 02 f8 bl #4 +// CHECK-NEXT: 2000004: 00 f0 06 f8 bl #12 +// CHECK: __Thumbv7ABSLongThunk_low_target: +// CHECK-NEXT: 2000008: 40 f2 03 0c movw r12, #3 +// CHECK-NEXT: 200000c: c0 f2 10 0c movt r12, #16 +// CHECK-NEXT: 2000010: 60 47 bx r12 + + .section orphan, "ax", %progbits + .thumb + .globl orphan_target + .type orphan_target, %function +orphan_target: + bl low_target + bl high_target +// CHECK: Disassembly of section orphan: +// CHECK-NEXT: orphan_target: +// CHECK-NEXT: 2000014: ff f7 f8 ff bl #-16 +// CHECK-NEXT: 2000018: ff f7 f2 ff bl #-28 + + .data + .word 10 Index: test/ELF/arm-thunk-linkerscript-sort.s =================================================================== --- /dev/null +++ test/ELF/arm-thunk-linkerscript-sort.s @@ -0,0 +1,71 @@ +// REQUIRES: arm +// RUN: llvm-mc -filetype=obj -triple=thumbv7a-none-linux-gnueabi %s -o %t +// RUN: echo "SECTIONS { \ +// RUN: .text 0x100000 : { *(SORT_BY_NAME(.text.*)) } \ +// RUN: }" > %t.script +// RUN: ld.lld --script %t.script %t -o %t2 2>&1 +// RUN: llvm-objdump -d %t2 -start-address=1048576 -stop-address=1048584 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK1 %s +// RUN: llvm-objdump -d %t2 -start-address=16777220 -stop-address=16777230 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK2 %s + + .syntax unified + +// Test that linkerscript sorting does not apply to Thunks, we expect that the +// sort will reverse the order of sections presented here. + +// Define a function aligned on a megabyte boundary + .macro FUNCTION suff + .section .text.\suff\(), "ax", %progbits + .thumb + .balign 0x100000 + .globl tfunc\suff\() + .type tfunc\suff\(), %function +tfunc\suff\(): + bx lr + .endm + + FUNCTION 31 + FUNCTION 30 + FUNCTION 29 + FUNCTION 28 + FUNCTION 27 + FUNCTION 26 + FUNCTION 25 + FUNCTION 24 + FUNCTION 23 + FUNCTION 22 + FUNCTION 21 + FUNCTION 20 + FUNCTION 19 + FUNCTION 18 + FUNCTION 17 + FUNCTION 16 + FUNCTION 15 +// CHECK2: __Thumbv7ABSLongThunk_tfunc31: +// CHECK2-NEXT: 1000004: 40 f2 01 0c movw r12, #1 +// CHECK2-NEXT: 1000008: c0 f2 00 2c movt r12, #512 +// CHECK2-NEXT: 100000c: 60 47 bx r12 + FUNCTION 14 + FUNCTION 13 + FUNCTION 12 + FUNCTION 11 + FUNCTION 10 + FUNCTION 09 + FUNCTION 08 + FUNCTION 07 + FUNCTION 06 + FUNCTION 05 + FUNCTION 04 + FUNCTION 03 + FUNCTION 02 + FUNCTION 01 + .section .text.00, "ax", %progbits + .thumb + .globl _start +_start: +// Expect no range extension needed for tfunc01 and an extension needed for +// tfunc31 + bl tfunc01 + bl tfunc31 +// CHECK1: _start: +// CHECK1-NEXT: 100000: ff f0 fe ff bl #1048572 +// CHECK1-NEXT: 100004: ff f2 fe d7 bl #15728636 Index: test/ELF/arm-thunk-linkerscript.s =================================================================== --- /dev/null +++ test/ELF/arm-thunk-linkerscript.s @@ -0,0 +1,78 @@ +// RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %s -o %t +// RUN: echo "SECTIONS { \ +// RUN: . = SIZEOF_HEADERS; \ +// RUN: .text_low : { *(.text_low) *(.text_low2) } \ +// RUN: .text_high 0x2000000 : { *(.text_high) *(.text_high2) } \ +// RUN: } " > %t.script +// RUN: ld.lld --script %t.script %t -o %t2 2>&1 +// RUN: llvm-objdump -d -triple=thumbv7a-none-linux-gnueabi %t2 | FileCheck %s + +// Simple test that we can support range extension thunks with linker scripts + .syntax unified + .section .text_low, "ax", %progbits + .thumb + .globl _start +_start: bx lr + .globl low_target + .type low_target, %function +low_target: + bl high_target + bl high_target2 + + .section .text_low2, "ax", %progbits + .thumb + .globl low_target2 + .type low_target2, %function +low_target2: + bl high_target + bl high_target2 + +// CHECK: Disassembly of section .text_low: +// CHECK-NEXT: _start: +// CHECK-NEXT: 94: 70 47 bx lr +// CHECK: low_target: +// CHECK-NEXT: 96: 00 f0 03 f8 bl #6 +// CHECK-NEXT: 9a: 00 f0 06 f8 bl #12 +// CHECK: __Thumbv7ABSLongThunk_high_target: +// CHECK-NEXT: a0: 40 f2 01 0c movw r12, #1 +// CHECK-NEXT: a4: c0 f2 00 2c movt r12, #512 +// CHECK-NEXT: a8: 60 47 bx r12 +// CHECK: __Thumbv7ABSLongThunk_high_target2: +// CHECK-NEXT: aa: 40 f2 1d 0c movw r12, #29 +// CHECK-NEXT: ae: c0 f2 00 2c movt r12, #512 +// CHECK-NEXT: b2: 60 47 bx r12 +// CHECK: low_target2: +// CHECK-NEXT: b4: ff f7 f4 ff bl #-24 +// CHECK-NEXT: b8: ff f7 f7 ff bl #-18 + + .section .text_high, "ax", %progbits + .thumb + .globl high_target + .type high_target, %function +high_target: + bl low_target + bl low_target2 + + .section .text_high2, "ax", %progbits + .thumb + .globl high_target2 + .type high_target2, %function +high_target2: + bl low_target + bl low_target2 + +// CHECK: Disassembly of section .text_high: +// CHECK-NEXT: high_target: +// CHECK-NEXT: 2000000: 00 f0 02 f8 bl #4 +// CHECK-NEXT: 2000004: 00 f0 05 f8 bl #10 +// CHECK: __Thumbv7ABSLongThunk_low_target: +// CHECK-NEXT: 2000008: 40 f2 97 0c movw r12, #151 +// CHECK-NEXT: 200000c: c0 f2 00 0c movt r12, #0 +// CHECK-NEXT: 2000010: 60 47 bx r12 +// CHECK: __Thumbv7ABSLongThunk_low_target2: +// CHECK-NEXT: 2000012: 40 f2 b5 0c movw r12, #181 +// CHECK-NEXT: 2000016: c0 f2 00 0c movt r12, #0 +// CHECK-NEXT: 200001a: 60 47 bx r12 +// CHECK: high_target2: +// CHECK-NEXT: 200001c: ff f7 f4 ff bl #-24 +// CHECK-NEXT: 2000020: ff f7 f7 ff bl #-18 Index: test/ELF/arm-thunk-multipass.s =================================================================== --- /dev/null +++ test/ELF/arm-thunk-multipass.s @@ -0,0 +1,96 @@ +// REQUIRES: arm +// RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %s -o %t +// RUN: ld.lld %t -o %t2 2>&1 +// The output file is large, most of it zeroes. We dissassemble only the +// parts we need to speed up the test and avoid a large output file +// RUN: llvm-objdump -d %t2 -start-address=1048578 -stop-address=1048586 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK1 %s +// RUN: llvm-objdump -d %t2 -start-address=16777224 -stop-address=16777254 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK2 %s +// RUN: llvm-objdump -d %t2 -start-address=17825818 -stop-address=17825828 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK3 %s +// In this test case a branch that is in range and does not need its range +// extended can be pushed out of range by another Thunk, necessitating another +// pass + + .macro FUNCTION suff + .section .text.\suff\(), "ax", %progbits + .thumb + .balign 0x100000 + .globl tfunc\suff\() + .type tfunc\suff\(), %function +tfunc\suff\(): + bx lr + .endm + + FUNCTION 00 + .globl _start +_start: + bl target + b.w arm_target +// arm_target is in range but needs an interworking thunk +// CHECK1: _start: +// CHECK1-NEXT: 100002: 00 f3 06 d0 bl #15728652 +// CHECK1-NEXT: 100006: ff f2 ff 97 b.w #15728638 <__Thumbv7ABSLongThunk_arm_target> + nop + nop + nop + .globl target2 + .type target2, %function + nop + +target2: + FUNCTION 01 + FUNCTION 02 + FUNCTION 03 + FUNCTION 04 + FUNCTION 05 + FUNCTION 06 + FUNCTION 07 + FUNCTION 08 + FUNCTION 09 + FUNCTION 10 + FUNCTION 11 + FUNCTION 12 + FUNCTION 13 + FUNCTION 14 + FUNCTION 15 + + .section .text.16, "ax", %progbits + .arm + .globl arm_target + .type arm_target, %function +arm_target: + bx lr +// CHECK2: __Thumbv7ABSLongThunk_arm_target: +// CHECK2-NEXT: 1000008: 40 f2 02 0c movw r12, #2 +// CHECK2-NEXT: 100000c: c0 f2 00 1c movt r12, #256 +// CHECK2-NEXT: 1000010: 60 47 bx r12 +// CHECK2: __Thumbv7ABSLongThunk_target: +// CHECK2-NEXT: 1000012: 40 f2 1b 0c movw r12, #27 +// CHECK2-NEXT: 1000016: c0 f2 10 1c movt r12, #272 +// CHECK2-NEXT: 100001a: 60 47 bx r12 +// CHECK2: __Thumbv7ABSLongThunk_target2: +// CHECK2-NEXT: 100001c: 40 f2 13 0c movw r12, #19 +// CHECK2-NEXT: 1000020: c0 f2 10 0c movt r12, #16 +// CHECK2-NEXT: 1000024: 60 47 bx r12 + + .section .text.17, "ax", %progbits +// Just enough space so that bl target is in range if no extension thunks are +// generated. + + .space 0x100000 - 12 + + .section .text.18, "ax", %progbits + .thumb + .globl target + .type target, %function +// target is at maximum ARM branch range away from caller. +target: +// Similar case in the backwards direction + bl target2 + nop + nop + bx lr +// CHECK3: target: +// CHECK3-NEXT: 110001a: ff f6 ff ff bl #-1048578 +// CHECK3-NEXT: 110001e: 00 bf nop +// CHECK3-NEXT: 1100020: 00 bf nop +// CHECK3-NEXT: 1100022: 70 47 bx lr Index: test/ELF/arm-thunk-re-add.s =================================================================== --- /dev/null +++ test/ELF/arm-thunk-re-add.s @@ -0,0 +1,119 @@ +// REQUIRES: arm +// RUN: llvm-mc -filetype=obj -triple=thumbv7a-none-linux-gnueabi %s -o %t +// RUN: ld.lld %t --shared -o %t.so +// The output file is large, most of it zeroes. We dissassemble only the +// parts we need to speed up the test and avoid a large output file +// RUN: llvm-objdump -d %t.so -start-address=16777220 -stop-address=16777244 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK1 %s +// RUN: llvm-objdump -d %t.so -start-address=17825800 -stop-address=17825826 -triple=thumbv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK2 %s +// RUN: llvm-objdump -d %t.so -start-address=17825824 -stop-address=17825876 -triple=armv7a-linux-gnueabihf | FileCheck -check-prefix=CHECK3 %s + +// A branch to a Thunk that we create on pass N, can drift out of range if +// other Thunks are added in between. In this case we must create a new Thunk +// for the branch that is in range. We also need to make sure that if the +// destination of the Thunk is in the PLT the new Thunk also targets the PLT + .syntax unified + .thumb + + .macro FUNCTION suff + .section .text.\suff\(), "ax", %progbits + .thumb + .balign 0x80000 + .globl tfunc\suff\() + .type tfunc\suff\(), %function +tfunc\suff\(): + bx lr + .endm + + .globl imported + .type imported, %function + .globl imported2 + .type imported2, %function + .globl imported3 + .type imported3, %function +.globl imported4 + .type imported4, %function + FUNCTION 00 + FUNCTION 01 + FUNCTION 02 + FUNCTION 03 + FUNCTION 04 + FUNCTION 05 + FUNCTION 06 + FUNCTION 07 + FUNCTION 08 + FUNCTION 09 + FUNCTION 10 + FUNCTION 11 + FUNCTION 12 + FUNCTION 13 + FUNCTION 14 + FUNCTION 15 + FUNCTION 16 + FUNCTION 17 + FUNCTION 18 + FUNCTION 19 + FUNCTION 20 + FUNCTION 21 + FUNCTION 22 + FUNCTION 23 + FUNCTION 24 + FUNCTION 25 + FUNCTION 26 + FUNCTION 27 + FUNCTION 28 + FUNCTION 29 + FUNCTION 30 + FUNCTION 31 +// Precreated Thunk Pool goes here +// CHECK1: 1000004: 40 f2 24 0c movw r12, #36 +// CHECK1-NEXT: 1000008: c0 f2 10 0c movt r12, #16 +// CHECK1-NEXT: 100000c: fc 44 add r12, pc +// CHECK1-NEXT: 100000e: 60 47 bx r12 +// CHECK1: __ThumbV7PILongThunk_imported2: +// CHECK1-NEXT: 1000010: 40 f2 28 0c movw r12, #40 +// CHECK1-NEXT: 1000014: c0 f2 10 0c movt r12, #16 +// CHECK1-NEXT: 1000018: fc 44 add r12, pc +// CHECK1-NEXT: 100001a: 60 47 bx r12 + + .section .text.32, "ax", %progbits + .space 0x80000 + .section .text.33, "ax", %progbits + .space 0x80000 - 0x14 + .section .text.34, "ax", %progbits + // Need a Thunk to the PLT entry, can use precreated ThunkSection + .globl callers + .type callers, %function +callers: + b.w imported + beq.w imported + b.w imported2 +// CHECK2: __ThumbV7PILongThunk_imported: +// CHECK2-NEXT: 1100008: 40 f2 20 0c movw r12, #32 +// CHECK2-NEXT: 110000c: c0 f2 00 0c movt r12, #0 +// CHECK2-NEXT: 1100010: fc 44 add r12, pc +// CHECK2-NEXT: 1100012: 60 47 bx r12 +// CHECK2: callers: +// CHECK2-NEXT: 1100014: ff f6 f6 bf b.w #-1048596 <__ThumbV7PILongThunk_imported> +// CHECK2-NEXT: 1100018: 3f f4 f6 af beq.w #-20 <__ThumbV7PILongThunk_imported> +// CHECK2-NEXT: 110001c: ff f6 f8 bf b.w #-1048592 <__ThumbV7PILongThunk_imported2> + +// CHECK3: Disassembly of section .plt: +// CHECK3-NEXT: $a: +// CHECK3-NEXT: 1100020: 04 e0 2d e5 str lr, [sp, #-4]! +// CHECK3-NEXT: 1100024: 04 e0 9f e5 ldr lr, [pc, #4] +// CHECK3-NEXT: 1100028: 0e e0 8f e0 add lr, pc, lr +// CHECK3-NEXT: 110002c: 08 f0 be e5 ldr pc, [lr, #8]! +// CHECK3: $d: +// CHECK3-NEXT: 1100030: d0 0f 00 00 .word 0x00000fd0 +// CHECK3: $a: +// CHECK3-NEXT: 1100034: 04 c0 9f e5 ldr r12, [pc, #4] +// CHECK3-NEXT: 1100038: 0f c0 8c e0 add r12, r12, pc +// CHECK3-NEXT: 110003c: 00 f0 9c e5 ldr pc, [r12] +// CHECK3: $d: +// CHECK3-NEXT: 1100040: cc 0f 00 00 .word 0x00000fcc +// CHECK3: $a: +// CHECK3-NEXT: 1100044: 04 c0 9f e5 ldr r12, [pc, #4] +// CHECK3-NEXT: 1100048: 0f c0 8c e0 add r12, r12, pc +// CHECK3-NEXT: 110004c: 00 f0 9c e5 ldr pc, [r12] +// CHECK3: $d: +// CHECK3-NEXT: 1100050: c0 0f 00 00 .word 0x00000fc0