[lld] [lld][macho] Restructure thunk generation algorithm (PR #193367)
Zhaoxuan Jiang via llvm-commits
llvm-commits at lists.llvm.org
Mon Apr 27 04:39:20 PDT 2026
================
@@ -276,171 +115,196 @@ void TextOutputSection::finalize() {
uint64_t forwardBranchRange = target->forwardBranchRange;
uint64_t backwardBranchRange = target->backwardBranchRange;
- uint64_t branchTargetThresholdVA = TargetInfo::outOfRangeVA;
size_t thunkSize = target->thunkSize;
- size_t relocCount = 0;
- size_t callSiteCount = 0;
size_t thunkCallCount = 0;
size_t thunkCount = 0;
- // Walk all sections in order. Finalize all sections that are less than
- // forwardBranchRange in front of it.
- // isecVA is the address of the current section.
- // addr + size is the start address of the first non-finalized section.
-
- // inputs[finalIdx] is for finalization (address-assignment)
- size_t finalIdx = 0;
- // Kick-off by ensuring that the first input section has an address
- for (size_t callIdx = 0, endIdx = inputs.size(); callIdx < endIdx;
- ++callIdx) {
- if (finalIdx == callIdx)
- finalizeOne(inputs[finalIdx++]);
- ConcatInputSection *isec = inputs[callIdx];
- assert(isec->isFinal);
- uint64_t isecVA = isec->getVA();
-
- // Assign addresses up-to the forward branch-range limit.
- // Every call instruction needs a small number of bytes (on Arm64: 4),
- // and each inserted thunk needs a slightly larger number of bytes
- // (on Arm64: 12). If a section starts with a branch instruction and
- // contains several branch instructions in succession, then the distance
- // from the current position to the position where the thunks are inserted
- // grows. So leave room for a bunch of thunks.
- unsigned slop = config->slopScale * thunkSize;
- while (finalIdx < endIdx) {
- uint64_t expectedNewSize =
- alignToPowerOf2(addr + size, inputs[finalIdx]->align) +
- inputs[finalIdx]->getSize();
- if (expectedNewSize >= isecVA + forwardBranchRange - slop)
+ auto isTargetInRange = [&](const ConcatInputSection &isec,
+ const Relocation &r) -> bool {
+ uint64_t callVA = isec.getVA() + r.offset;
+ uint64_t lowVA =
+ backwardBranchRange < callVA ? callVA - backwardBranchRange : 0;
+ uint64_t highVA = callVA + forwardBranchRange;
+ auto *funcSym = cast<Symbol *>(r.referent);
+ uint64_t funcVA = resolveSymbolOffsetVA(funcSym, r.type, r.addend);
+ // Check if the referent is reachable with a simple call instruction.
+ return lowVA <= funcVA && funcVA <= highVA;
+ };
+ auto getThunkInRange = [&](const ConcatInputSection &isec,
+ const Relocation &r) -> Defined * {
+ assert(!isTargetInRange(isec, r));
+ uint64_t callVA = isec.getVA() + r.offset;
+ uint64_t lowVA =
+ backwardBranchRange < callVA ? callVA - backwardBranchRange : 0;
+ uint64_t highVA = callVA + forwardBranchRange;
+ auto *funcSym = cast<Symbol *>(r.referent);
+ ThunkInfo &thunkInfo = thunkMap[ThunkKey{funcSym, r.addend}];
+ if (thunkInfo.sym) {
+ uint64_t thunkVA = thunkInfo.isec->getVA();
+ if (lowVA <= thunkVA && thunkVA <= highVA)
+ return thunkInfo.sym;
+ }
+ return nullptr;
+ };
+ auto createThunk = [&](const ConcatInputSection &isec, Relocation &r) {
+ assert(getThunkInRange(isec, r) == nullptr);
+ assert(isec.isFinal);
+ uint64_t highVA = isec.getVA() + r.offset + forwardBranchRange;
+ if (addr + size > highVA) {
+ // There were too many consecutive branch instructions for `slop`
+ // below. If you hit this: For the current algorithm, just bumping up
+ // slop above and trying again is probably simplest. (See also PR51578
+ // comment 5).
+ fatal(Twine(__FUNCTION__) +
+ ": FIXME: thunk range overrun. Consider increasing the "
+ "slop-scale with `--slop-scale=<unsigned_int>`.");
+ }
+ auto *funcSym = cast<Symbol *>(r.referent);
+ ThunkInfo &thunkInfo = thunkMap[ThunkKey{funcSym, r.addend}];
+ thunkInfo.isec =
+ makeSyntheticInputSection(isec.getSegName(), isec.getName());
+ thunkInfo.isec->parent = this;
+ assert(thunkInfo.isec->live);
+
+ std::string addendSuffix;
+ if (r.addend != 0)
+ addendSuffix = "+" + std::to_string(r.addend);
+ StringRef thunkName =
+ saver().save(funcSym->getName() + addendSuffix + ".thunk." +
+ std::to_string(thunkInfo.sequence++));
+ if (!isa<Defined>(funcSym) || cast<Defined>(funcSym)->isExternal()) {
+ r.referent = thunkInfo.sym = symtab->addDefined(
+ thunkName, /*file=*/nullptr, thunkInfo.isec, /*value=*/0, thunkSize,
+ /*isWeakDef=*/false, /*isPrivateExtern=*/true,
+ /*isReferencedDynamically=*/false, /*noDeadStrip=*/false,
+ /*isWeakDefCanBeHidden=*/false);
+ } else {
+ r.referent = thunkInfo.sym = make<Defined>(
+ thunkName, /*file=*/nullptr, thunkInfo.isec, /*value=*/0, thunkSize,
+ /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/true,
+ /*includeInSymtab=*/true, /*isReferencedDynamically=*/false,
+ /*noDeadStrip=*/false, /*isWeakDefCanBeHidden=*/false);
+ }
+ thunkInfo.sym->used = true;
+ target->populateThunk(thunkInfo.isec, funcSym, r.addend);
+ // The thunk itself bakes in the addend, so the call-site reloc must
+ // branch to the thunk start with no extra offset.
+ r.addend = 0;
+ ++thunkCallCount;
+ finalizeOne(thunkInfo.isec);
+ thunks.push_back(thunkInfo.isec);
+ ++thunkCount;
+ };
+
+ std::deque<std::tuple<ConcatInputSection *, Relocation *, ThunkKey>>
+ branchesToProcess;
+ SmallVector<std::tuple<ConcatInputSection *, Relocation *, Defined *>>
+ deferredBranchRedirects;
+
+ const uint64_t slop = config->slopScale * thunkSize;
+ for (auto *isec : inputs) {
+ while (!branchesToProcess.empty()) {
+ auto &[callerIsec, r, thunkKey] = branchesToProcess.front();
+ assert(callerIsec->isFinal);
+ if (isTargetInRange(*callerIsec, *r)) {
+ branchesToProcess.pop_front();
+ continue;
+ }
+ if (auto *sym = getThunkInRange(*callerIsec, *r)) {
+ deferredBranchRedirects.emplace_back(callerIsec, r, sym);
+ branchesToProcess.pop_front();
+ continue;
+ }
+ uint64_t highVA = callerIsec->getVA() + r->offset + forwardBranchRange;
+ uint64_t nextEnd =
+ alignToPowerOf2(addr + size, isec->align) + isec->getSize();
+ if (nextEnd + slop <= highVA)
break;
- finalizeOne(inputs[finalIdx++]);
+
+ createThunk(*callerIsec, *r);
+ branchesToProcess.pop_front();
}
+ finalizeOne(isec);
- if (!isec->hasCallSites)
+ bool hasCallsite = llvm::any_of(isec->relocs, [](Relocation &r) {
+ return target->hasAttr(r.type, RelocAttrBits::BRANCH);
+ });
+ if (!hasCallsite)
----------------
nocchijiang wrote:
I don't think @drodriguez was asking to remove the assert in this PR. I agree we should not try to change the assert semantics in this PR, but I do want this assert to go away eventually. We have `ld64 -r` inputs whose reloc order does not satisfy LLD’s current expectation, and our local workaround is quite ugly: we build a pointer copy of the branch relocs and sort that copy just to satisfy the assumption here.
https://github.com/llvm/llvm-project/pull/193367
More information about the llvm-commits
mailing list