-
Notifications
You must be signed in to change notification settings - Fork 13.5k
ELF: Add branch-to-branch optimization. #138366
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
//===- TargetImpl.h ---------------------------------------------*- C++ -*-===// | ||
// | ||
// 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 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#ifndef LLD_ELF_ARCH_TARGETIMPL_H | ||
#define LLD_ELF_ARCH_TARGETIMPL_H | ||
|
||
#include "InputFiles.h" | ||
#include "InputSection.h" | ||
#include "Relocations.h" | ||
#include "Symbols.h" | ||
#include "llvm/BinaryFormat/ELF.h" | ||
|
||
namespace lld { | ||
namespace elf { | ||
|
||
// getControlTransferAddend: If this relocation is used for control transfer | ||
// instructions (e.g. branch, branch-link or call) or code references (e.g. | ||
// virtual function pointers) and indicates an address-insignificant reference, | ||
// return the effective addend for the relocation, otherwise return | ||
// std::nullopt. The effective addend for a relocation is the addend that is | ||
// used to determine its branch destination. | ||
// | ||
// getBranchInfo: If a control transfer relocation referring to is+offset | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we name this Maybe just me, but I had to do some double checks to make sure I had the right one. |
||
// directly transfers control to a relocated branch instruction in the specified | ||
// section, return the relocation for the branch target as well as its effective | ||
// addend (see above). Otherwise return {nullptr, 0}. | ||
// | ||
// mergeControlTransferRelocations: Given r1, a relocation for which | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a personal preference, I think |
||
// getControlTransferAddend() returned a value, and r2, a relocation returned by | ||
// getBranchInfo(), modify r1 so that it branches directly to the target of r2. | ||
template <typename GetBranchInfo, typename GetControlTransferAddend, | ||
typename MergeControlTransferRelocations> | ||
inline void applyBranchToBranchOptImpl( | ||
Ctx &ctx, GetBranchInfo getBranchInfo, | ||
GetControlTransferAddend getControlTransferAddend, | ||
MergeControlTransferRelocations mergeControlTransferRelocations) { | ||
// Needs to run serially because it writes to the relocations array as well as | ||
// reading relocations of other sections. | ||
for (ELFFileBase *f : ctx.objectFiles) { | ||
auto getRelocBranchInfo = | ||
[&getBranchInfo](Relocation &r, | ||
uint64_t addend) -> std::pair<Relocation *, uint64_t> { | ||
auto *target = dyn_cast_or_null<Defined>(r.sym); | ||
// We don't allow preemptible symbols or ifuncs (may go somewhere else), | ||
// absolute symbols (runtime behavior unknown), non-executable memory | ||
// (ditto) or non-regular sections (no section data). | ||
if (!target || target->isPreemptible || target->isGnuIFunc() || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While uncommon, it is possible to have SHT_REL relocs which may have a non zero addend. I know of at least one tool that can generate them. I don't think these need to be supported, but could be worth skipping any that are encountered. |
||
!target->section || | ||
!(target->section->flags & llvm::ELF::SHF_EXECINSTR) || | ||
target->section->kind() != SectionBase::Regular) | ||
return {nullptr, 0}; | ||
return getBranchInfo(*cast<InputSection>(target->section), | ||
target->value + addend); | ||
}; | ||
for (InputSectionBase *s : f->getSections()) { | ||
if (!s) | ||
continue; | ||
for (Relocation &r : s->relocations) { | ||
if (std::optional<uint64_t> addend = | ||
getControlTransferAddend(*cast<InputSection>(s), r)) { | ||
std::pair<Relocation *, uint64_t> targetAndAddend = | ||
getRelocBranchInfo(r, *addend); | ||
if (targetAndAddend.first) { | ||
// Avoid getting stuck in an infinite loop if we encounter a branch | ||
// that (possibly indirectly) branches to itself. It is unlikely | ||
// that more than 5 iterations will ever be needed in practice. | ||
size_t iterations = 5; | ||
while (iterations--) { | ||
std::pair<Relocation *, uint64_t> nextTargetAndAddend = | ||
getRelocBranchInfo(*targetAndAddend.first, | ||
targetAndAddend.second); | ||
if (!nextTargetAndAddend.first) | ||
break; | ||
targetAndAddend = nextTargetAndAddend; | ||
} | ||
mergeControlTransferRelocations(r, *targetAndAddend.first); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
} // namespace elf | ||
} // namespace lld | ||
|
||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you know that the incoming relocation is
R_AARCH64_JUMP26
orR_AARCH64_CALL26
, and the destination is a BTI, then in theory you could check to see if the next instruction was a direct branch.Probably unlikely enough to not need handling though.
NOPs, at least those at the start of a function, are probably not safe to skip as these can be used for hot-patching.