Index: ELF/LinkerScript.h =================================================================== --- ELF/LinkerScript.h +++ ELF/LinkerScript.h @@ -271,7 +271,7 @@ bool needsInterpSection(); bool shouldKeep(InputSectionBase *s); - void assignAddresses(); + const Defined *assignAddresses(); void allocateHeaders(std::vector &phdrs); void processSectionCommands(); void declareSymbols(); Index: ELF/LinkerScript.cpp =================================================================== --- ELF/LinkerScript.cpp +++ ELF/LinkerScript.cpp @@ -1054,7 +1054,9 @@ // Here we assign addresses as instructed by linker script SECTIONS // sub-commands. Doing that allows us to use final VA values, so here // we also handle rest commands like symbol assignments and ASSERTs. -void LinkerScript::assignAddresses() { +// Returns a symbol that has changed its section or value, or nullptr if no +// symbol has changed. +const Defined *LinkerScript::assignAddresses() { dot = getInitialDot(); auto deleter = std::make_unique(); @@ -1062,6 +1064,21 @@ errorOnMissingSection = true; switchTo(aether); + DenseMap> old; + for (BaseCommand *base : sectionCommands) { + if (auto *cmd = dyn_cast(base)) { + if (cmd->sym) + old.try_emplace(cmd->sym, + std::make_pair(cmd->sym->section, cmd->sym->value)); + continue; + } + for (BaseCommand *base1 : cast(base)->sectionCommands) + if (auto *cmd = dyn_cast(base1)) + if (cmd->sym) + old.try_emplace(cmd->sym, + std::make_pair(cmd->sym->section, cmd->sym->value)); + } + for (BaseCommand *base : sectionCommands) { if (auto *cmd = dyn_cast(base)) { cmd->addr = dot; @@ -1072,6 +1089,17 @@ assignOffsets(cast(base)); } ctx = nullptr; + + // Return the lexicographical smallest (for determinism) Defined whose + // section/value has changed. + const Defined *changed = nullptr; + for (auto &it : old) { + const Defined *sym = it.first; + if ((std::make_pair(sym->section, sym->value) != it.second) && + (!changed || sym->getName() < changed->getName())) + changed = sym; + } + return changed; } // Creates program headers as instructed by PHDRS linker script command. Index: ELF/Writer.cpp =================================================================== --- ELF/Writer.cpp +++ ELF/Writer.cpp @@ -580,8 +580,6 @@ if (errorCount()) return; - script->assignAddresses(); - // If -compressed-debug-sections is specified, we need to compress // .debug_* sections. Do it right now because it changes the size of // output sections. @@ -1567,15 +1565,12 @@ template void Writer::finalizeAddressDependentContent() { ThunkCreator tc; AArch64Err843419Patcher a64p; + script->assignAddresses(); // For some targets, like x86, this loop iterates only once. + int tries = 5; for (;;) { - bool changed = false; - - script->assignAddresses(); - - if (target->needsThunks) - changed |= tc.createThunks(outputSections); + bool changed = target->needsThunks && tc.createThunks(outputSections); if (config->fixCortexA53Errata843419) { if (changed) @@ -1592,8 +1587,19 @@ changed |= part.relrDyn->updateAllocSize(); } - if (!changed) - return; + const Defined *changedSym = script->assignAddresses(); + if (!changed) { + // Some symbols may be dependent on section addresses. When we break the + // loop, the symbol values are finalized because a previous + // assignAddresses() finalized section addresses. + if (!changedSym) + break; + if (--tries == 0) { + errorOrWarn("assignment to symbol " + toString(*changedSym) + + " does not converge"); + break; + } + } } } Index: test/ELF/linkerscript/expr-converge.test =================================================================== --- /dev/null +++ test/ELF/linkerscript/expr-converge.test @@ -0,0 +1,25 @@ +# REQUIRES: aarch64, x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64 /dev/null -o %t.o +# RUN: ld.lld %t.o -T %s -o %t +# RUN: llvm-nm %t | FileCheck %s + +## AArch64 needs thunks and has different address finalization process, so test +## it as well. +# RUN: llvm-mc -filetype=obj -triple=aarch64 /dev/null -o %t.o +# RUN: ld.lld %t.o -T %s -o %t +# RUN: llvm-nm %t | FileCheck %s + +# CHECK: 0000000000001004 T a +# CHECK: 0000000000001003 T b +# CHECK: 0000000000001002 T c +# CHECK: 0000000000001001 T d +# CHECK: 0000000000001000 T e + +SECTIONS { + . = 0x1000; + a = b + 1; + b = c + 1; + c = d + 1; + d = e + 1; + e = .; +} Index: test/ELF/linkerscript/expr-converge2.test =================================================================== --- /dev/null +++ test/ELF/linkerscript/expr-converge2.test @@ -0,0 +1,27 @@ +# REQUIRES: aarch64, x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64 /dev/null -o %t.o +# RUN: ld.lld %t.o -T %s -o %t +# RUN: llvm-nm %t | FileCheck %s + +## AArch64 needs thunks and has different address finalization process, so test +## it as well. +# RUN: llvm-mc -filetype=obj -triple=aarch64 /dev/null -o %t.o +# RUN: ld.lld %t.o -T %s -o %t +# RUN: llvm-nm %t | FileCheck %s + +# CHECK: 0000000000001004 T a +# CHECK: 0000000000001003 T b +# CHECK: 0000000000001002 T c +# CHECK: 0000000000001001 T d +# CHECK: 0000000000001000 T e + +SECTIONS { + . = 0x1000; + .text : { + a = b + 1; + b = c + 1; + c = d + 1; + d = e + 1; + e = .; + } +} Index: test/ELF/linkerscript/expr-not-converge.test =================================================================== --- /dev/null +++ test/ELF/linkerscript/expr-not-converge.test @@ -0,0 +1,20 @@ +# REQUIRES: aarch64, x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64 /dev/null -o %t.o +# RUN: not ld.lld %t.o -T %s -o /dev/null 2>&1 | FileCheck %s + +## AArch64 needs thunks and has different address finalization process, so test +## it as well. +# RUN: llvm-mc -filetype=obj -triple=aarch64 /dev/null -o %t.o +# RUN: not ld.lld %t.o -T %s -o /dev/null 2>&1 | FileCheck %s + +# CHECK: error: assignment to symbol a does not converge + +SECTIONS { + . = 0x1000; + a = b; + b = c; + c = d; + d = e; + e = f; + f = .; +}