diff --git a/include/eld/Diagnostics/DiagVerbose.inc b/include/eld/Diagnostics/DiagVerbose.inc index 03c68c76b..5ac299c39 100644 --- a/include/eld/Diagnostics/DiagVerbose.inc +++ b/include/eld/Diagnostics/DiagVerbose.inc @@ -36,6 +36,9 @@ DIAG(relax_to_compress, DiagnosticEngine::Verbose, "%3 in section %4+0x%5 file %6") DIAG(trace_relax_gotpcrelx, DiagnosticEngine::Trace, "%0: relaxed GOTPCRELX (%1) for symbol '%2' to PC-relative access") +DIAG(trace_relax_gottpoff, DiagnosticEngine::Trace, + "relaxed GOTTPOFF (%0) for symbol '%1' to TP-relative immediate in " + "section %2+0x%3 file %4") DIAG(verbose_ehframe_remove_fde, DiagnosticEngine::Verbose, "EhFrame %0") DIAG(verbose_ehframe_read_fde, DiagnosticEngine::Verbose, "EhFrame %0") DIAG(verbose_ehframe_read_cie, DiagnosticEngine::Verbose, "EhFrame %0") diff --git a/lib/Target/X86/x86_64LDBackend.cpp b/lib/Target/X86/x86_64LDBackend.cpp index b5237a4f5..8b0f8c5cd 100644 --- a/lib/Target/X86/x86_64LDBackend.cpp +++ b/lib/Target/X86/x86_64LDBackend.cpp @@ -151,7 +151,46 @@ bool x86_64LDBackend::isGOTPCRELXRelaxable(const Relocation *reloc) const { } bool x86_64LDBackend::shouldIgnoreRelocSync(Relocation *reloc) const { - return isGOTPCRELXRelaxCandidate(reloc); + return isGOTPCRELXRelaxCandidate(reloc) || isTLSIERelaxCandidate(reloc); +} + +bool x86_64LDBackend::isTLSIERelaxable(const Relocation *reloc) const { + // Partial links have no final layout; keep the GOT slot + if (config().isLinkPartial()) + return false; + if (reloc->type() != llvm::ELF::R_X86_64_GOTTPOFF) + return false; + // GNU as emits GOTTPOFF with addend -4. An object with a different addend + // does not encode a standard gottpoff(%rip) load and cannot be rewritten. + if (static_cast(reloc->addend()) != -4) + return false; + // TLS symbols (STT_TLS) cannot be STT_GNU_IFUNC, so no IFUNC guard is + // needed here unlike isGOTPCRELXRelaxable. + auto *RF = llvm::dyn_cast(reloc->targetRef()->frag()); + if (!RF) + return false; + uint32_t offset = reloc->targetRef()->offset(); + // The rewrite reads REX at loc[-3], opcode at loc[-2], ModR/M at loc[-1]. + if (offset < 3) + return false; + const uint8_t *loc = + reinterpret_cast(RF->getRegion().data()) + offset; + uint8_t opcode = loc[-2]; + // Only MOV (0x8b) and ADD (0x03) GOTTPOFF forms are relaxable. + return opcode == 0x8b || opcode == 0x03; +} + +bool x86_64LDBackend::shouldRelaxTLSIEToLE(const Relocation *reloc, + bool pPreemptible) const { + // Unifies the two scan-call-sites: local (pPreemptible=false) and global. + // IE->LE is a mandatory ABI TLS transition. + if (!isTLSIERelaxable(reloc)) + return false; + // Relax when the TP offset is a link-time constant: any executable + // (static, dynamic, or PIE) for non-preemptible symbols. Shared libraries + // are never relaxed even for hidden symbols — the loader places the DSO TLS + // block at a runtime-determined offset. + return !pPreemptible && config().isBuildingExecutable(); } eld::Expected @@ -160,7 +199,7 @@ x86_64LDBackend::postProcessing(llvm::FileOutputBuffer &pOutput) { // Relaxation rewrites bytes in the laid-out output image; it is not // applicable to partial links, which have no final layout. - if (config().options().getRelax() && !config().isLinkPartial()) + if (!config().isLinkPartial()) ELDEXP_RETURN_DIAGENTRY_IF_ERROR(doRelax(pOutput)); return {}; @@ -168,21 +207,23 @@ x86_64LDBackend::postProcessing(llvm::FileOutputBuffer &pOutput) { eld::Expected x86_64LDBackend::doRelax(llvm::FileOutputBuffer &pOutput) { uint8_t *buf = pOutput.getBufferStart(); - // The scan phase already identified every relaxation candidate (skipping - // debug relocations and internal files, which never carry a relaxable - // relocation). Iterate that cached set instead of re-walking all - // relocations, and dispatch on the relocation type. Future relaxable types - // (e.g. R_X86_64_REX_GOTPCRELX, TLS relaxations) add a case here. - for (Relocation *reloc : m_GOTPCRELXRelaxCandidates) { - switch (reloc->type()) { - case llvm::ELF::R_X86_64_GOTPCRELX: - ELDEXP_RETURN_DIAGENTRY_IF_ERROR(relaxGOTPCRELXReloc(reloc, buf)); - break; - default: - // Only relaxable types are recorded as candidates; skip anything else. - break; + // The scan phase already identified every relaxation candidate. + // GOTPCRELX relaxation is optional (gated on --relax); IE→LE is a mandatory + // ABI TLS transition and runs unconditionally (mirrors GD→LE). + if (config().options().getRelax()) { + for (Relocation *reloc : m_GOTPCRELXRelaxCandidates) { + switch (reloc->type()) { + case llvm::ELF::R_X86_64_GOTPCRELX: + ELDEXP_RETURN_DIAGENTRY_IF_ERROR(relaxGOTPCRELXReloc(reloc, buf)); + break; + default: + // Only relaxable types are recorded as candidates; skip anything else. + break; + } } } + for (Relocation *reloc : m_TLSIERelaxCandidates) + ELDEXP_RETURN_DIAGENTRY_IF_ERROR(relaxTLSIEReloc(reloc, buf)); return {}; } @@ -285,6 +326,90 @@ eld::Expected x86_64LDBackend::relaxGOTPCRELXReloc(Relocation *reloc, return {}; } +eld::Expected x86_64LDBackend::relaxTLSIEReloc(Relocation *reloc, + uint8_t *buf) { + // Only RegionFragment relocations can carry GOTTPOFF (object file code + // sections), and offset >= 3 is guaranteed by isTLSIERelaxable in scan. + auto *RF = llvm::cast(reloc->targetRef()->frag()); + + uint32_t offset = reloc->targetRef()->offset(); + assert(offset >= 3 && "GOTTPOFF IE→LE relax candidate offset must be >= 3"); + + const uint8_t *loc = + reinterpret_cast(RF->getRegion().data()) + offset; + + uint64_t TLSTemplateSize = getTLSTemplateSize(); + if (TLSTemplateSize == 0) { + config().raise(Diag::no_pt_tls_segment); + return {}; + } + + // finalizeTLSSymbol gives the PT_TLS-relative offset: sym_VA - tls_vaddr. + // The TP-relative offset for Variant 2 (x86-64) is S - TLSTemplateSize. + // The GOTTPOFF addend (-4) is a PC-relative displacement artifact of the + // original GOT-indirect load; it must NOT be included in the immediate. + uint64_t S = finalizeTLSSymbol(reloc->symInfo()->outSymbol()); + int64_t tpoff = + static_cast(S) - static_cast(TLSTemplateSize); + uint32_t imm32 = static_cast(tpoff); + + FragmentRef::Offset off = reloc->targetRef()->getOutputOffset(m_Module); + if (off == (FragmentRef::Offset)-1) + return {}; + size_t out_off = reloc->targetRef()->getOutputELFSection()->offset() + off; + + uint8_t rex = loc[-3]; + uint8_t opcode = loc[-2]; + uint8_t modrm = loc[-1]; + uint8_t reg = (modrm >> 3) & 0x7; + + if (opcode == 0x8b) { + // movq gottpoff(%rip), %reg -> movq $tpoff, %reg + // REX.R (encodes reg field source) is no longer needed; REX.B (bit 0) + // selects r8-r15 in the 0xc0|reg ModRM. Map: 0x4c -> 0x49, else 0x48. + buf[out_off - 3] = (rex == 0x4c) ? 0x49 : 0x48; + buf[out_off - 2] = 0xc7; + buf[out_off - 1] = 0xc0 | reg; + llvm::support::endian::write32le(buf + out_off, imm32); + } else { + assert(opcode == 0x03 && + "isTLSIERelaxable only passes 0x8b and 0x03 opcodes"); + if (modrm == 0x25) { + // addq gottpoff(%rip), %rsp/%r12 -> addq $tpoff, %rsp/%r12 + // leaq would need a SIB byte (8 bytes total); addq $imm stays at 7. + buf[out_off - 3] = (rex == 0x4c) ? 0x49 : 0x48; + buf[out_off - 2] = 0x81; + buf[out_off - 1] = 0xc4; + llvm::support::endian::write32le(buf + out_off, imm32); + } else { + // addq gottpoff(%rip), %reg -> leaq tpoff(%reg), %reg + // For r8-r15 (REX was 0x4c), the new form needs REX.W + REX.R + REX.B + // because both the base and destination are the same extended register. + buf[out_off - 3] = (rex == 0x4c) ? 0x4d : 0x48; + buf[out_off - 2] = 0x8d; + buf[out_off - 1] = 0x80 | (reg << 3) | reg; + llvm::support::endian::write32le(buf + out_off, imm32); + } + } + + if (m_Module.getPrinter()->traceRelax()) { + ResolveInfo *rsym = reloc->symInfo(); + ELFSection *traceSect = RF->getOwningSection(); + assert(traceSect && + "RegionFragment in relax candidate must have an owning section"); + std::string fileName; + if (InputFile *F = traceSect->getInputFile()) + if (auto *I = F->getInput()) + fileName = I->decoratedPath(); + const char *kind = (opcode == 0x8b) ? "mov->movimm" : "add->lea/addimm"; + config().raise(Diag::trace_relax_gottpoff) + << kind << rsym->name() << traceSect->name() << llvm::utohexstr(offset) + << fileName; + } + + return {}; +} + /// finalizeSymbol - finalize the symbol value bool x86_64LDBackend::finalizeTargetSymbols() { if (config().codeGenType() == LinkerConfig::Object) diff --git a/lib/Target/X86/x86_64LDBackend.h b/lib/Target/X86/x86_64LDBackend.h index 6a5d5432f..ebf473a45 100644 --- a/lib/Target/X86/x86_64LDBackend.h +++ b/lib/Target/X86/x86_64LDBackend.h @@ -109,6 +109,18 @@ class x86_64LDBackend : public GNULDBackend { /// fragment). Relaxation is disabled for partial links. bool isGOTPCRELXRelaxable(const Relocation *reloc) const; + /// Returns true if a GOTTPOFF relocation is eligible for IE->LE relaxation. + /// Checks: not a partial link, type R_X86_64_GOTTPOFF, addend == -4, + /// RegionFragment with offset >= 3, and opcode is MOV (0x8b) or ADD (0x03). + /// Non-preemptibility and output-is-executable are checked in the scan. + bool isTLSIERelaxable(const Relocation *reloc) const; + + /// Returns true if the GOTTPOFF scan should record pReloc as an IE->LE + /// candidate and skip GOT creation. Combines isTLSIERelaxable and the + /// link-type / preemptibility condition. IE->LE is a mandatory ABI + /// transition. + bool shouldRelaxTLSIEToLE(const Relocation *reloc, bool pPreemptible) const; + /// Records a relocation that the scan phase has decided to relax, so that /// postProcessing can iterate only these candidates instead of re-walking /// every relocation. Thread-safe: called from the parallel scan under the @@ -124,6 +136,19 @@ class x86_64LDBackend : public GNULDBackend { return m_GOTPCRELXRelaxCandidates.count(reloc) != 0; } + /// Records a GOTTPOFF relocation whose scan phase decided on IE→LE + /// relaxation (a non-preemptible symbol in an executable). The GOT slot is + /// not allocated; postProcessing rewrites the instruction bytes. + void recordTLSIERelaxCandidate(Relocation *reloc) { + m_TLSIERelaxCandidates.insert(reloc); + } + + /// O(1) membership check used by shouldIgnoreRelocSync and the apply-path + /// guard in relocGOTRelative. + bool isTLSIERelaxCandidate(Relocation *reloc) const { + return m_TLSIERelaxCandidates.count(reloc) != 0; + } + DynRelocType getDynRelocType(const Relocation *X) const override { if (X->type() == llvm::ELF::R_X86_64_GLOB_DAT) return DynRelocType::GLOB_DAT; @@ -179,6 +204,10 @@ class x86_64LDBackend : public GNULDBackend { /// signed 32-bit field. eld::Expected relaxGOTPCRELXReloc(Relocation *reloc, uint8_t *buf); + /// Rewrites a single GOTTPOFF IE→LE candidate: patches REX, opcode, + /// ModR/M, and the 4-byte immediate in the output buffer. + eld::Expected relaxTLSIEReloc(Relocation *reloc, uint8_t *buf); + /// Iterates the cached relaxation candidates and rewrites each one. eld::Expected doRelax(llvm::FileOutputBuffer &pOutput); @@ -198,6 +227,10 @@ class x86_64LDBackend : public GNULDBackend { /// postProcessing. unordered_set for O(1) membership checks in /// shouldIgnoreRelocSync and the apply-path guard. std::unordered_set m_GOTPCRELXRelaxCandidates; + + /// Relocations selected for GOTTPOFF IE→LE relaxation during the scan + /// phase (non-preemptible symbols in executables). + std::unordered_set m_TLSIERelaxCandidates; }; } // namespace eld diff --git a/lib/Target/X86/x86_64Relocator.cpp b/lib/Target/X86/x86_64Relocator.cpp index ccb52fdc9..40c13ef6e 100644 --- a/lib/Target/X86/x86_64Relocator.cpp +++ b/lib/Target/X86/x86_64Relocator.cpp @@ -276,6 +276,11 @@ void x86_64Relocator::scanLocalReloc(InputFile &pInputFile, Relocation &pReloc, return; case llvm::ELF::R_X86_64_GOTTPOFF: { std::lock_guard relocGuard(m_RelocMutex); + // Local symbols are never preemptible; pass false directly. + if (m_Target.shouldRelaxTLSIEToLE(&pReloc, /*preemptible=*/false)) { + m_Target.recordTLSIERelaxCandidate(&pReloc); + return; + } if (rsym->reserved() & ReserveGOT) return; x86_64GOT *G = m_Target.createGOT(GOT::TLS_IE, Obj, rsym); @@ -459,12 +464,17 @@ void x86_64Relocator::scanGlobalReloc(InputFile &pInputFile, Relocation &pReloc, } case llvm::ELF::R_X86_64_GOTTPOFF: { std::lock_guard relocGuard(m_RelocMutex); + const bool preemptible = m_Target.isSymbolPreemptible(*rsym); + if (m_Target.shouldRelaxTLSIEToLE(&pReloc, preemptible)) { + m_Target.recordTLSIERelaxCandidate(&pReloc); + return; + } if (rsym->reserved() & ReserveGOT) return; x86_64GOT *G = m_Target.createGOT(GOT::TLS_IE, Obj, rsym); - const bool isExec = config().isBuildingExecutable(); - const bool preemptible = m_Target.isSymbolPreemptible(*rsym); - if (isExec && !preemptible) { + if (config().isBuildingExecutable() && !preemptible) { + // TP offset is fixed at link time for non-preemptible symbols in + // executables. Compute it at layout; no dynamic reloc needed. G->setValueType(GOT::TLSStaticSymbolValue); } else { helper_DynRel_init(Obj, &pReloc, rsym, G, 0x0, @@ -738,6 +748,11 @@ Relocator::Result eld::relocGOTRelative(Relocation &pReloc, if (pParent.getTarget().isGOTPCRELXRelaxCandidate(&pReloc)) return Relocator::OK; + // IE→LE candidates also skip GOT lookup; the byte rewrite already happened + // in postProcessing. + if (pParent.getTarget().isTLSIERelaxCandidate(&pReloc)) + return Relocator::OK; + Relocator::DWord A = pReloc.addend(); Relocator::DWord P = pReloc.place(pParent.module()); x86_64GOT *gotEntry = pParent.getTarget().findEntryInGOT(symInfo); diff --git a/test/x86_64/linux/TLSIELocalSymbol/TLSIELocalSymbol.test b/test/x86_64/linux/TLSIELocalSymbol/TLSIELocalSymbol.test index 07e856414..06195bf39 100644 --- a/test/x86_64/linux/TLSIELocalSymbol/TLSIELocalSymbol.test +++ b/test/x86_64/linux/TLSIELocalSymbol/TLSIELocalSymbol.test @@ -1,21 +1,26 @@ /// Verifies that R_X86_64_GOTTPOFF against a file-local TLS symbol emits a -/// R_X86_64_TPOFF64 dynamic relocation in -shared output, and is statically -/// resolved into the GOT slot in -pie. +/// R_X86_64_TPOFF64 dynamic relocation in -shared output. In -pie the symbol +/// is non-preemptible, so IE->LE relaxation (a mandatory ABI transition, not +/// gated on --relax) rewrites the GOT-indirect load to a TP-relative +/// immediate and drops the GOT slot entirely, matching lld. // RUN: %clang %clangopts -c -x c %s -fPIC -ftls-model=initial-exec -o %t.o // RUN: %link %linkopts -shared %t.o -o %t.shared.so // RUN: %readelf -r -x .got -d %t.shared.so | %filecheck %s --check-prefix=SHARED // RUN: %link %linkopts -pie %t.o -o %t.pie --defsym __libc_start_main=0 -// RUN: %readelf -r -x .got %t.pie | %filecheck %s --check-prefix=PIE +// RUN: %objdump -d %t.pie | %filecheck %s --check-prefix=PIE +// RUN: %readelf -r %t.pie | %filecheck %s --check-prefix=PIERELOC // SHARED: STATIC_TLS // SHARED: R_X86_64_TPOFF64 // SHARED-LABEL: Hex dump of section '.got': // SHARED-NEXT: 0x{{[0-9a-f]+}} 00000000 00000000 -// PIE-NOT: R_X86_64_TPOFF64 -// PIE-LABEL: Hex dump of section '.got': -// PIE-NEXT: 0x{{[0-9a-f]+}} fcffffff ffffffff +// IE->LE relaxes the movq gottpoff(%rip),%rcx load to movq $tpoff,%rcx +// (48 c7 c1 ); no GOT slot and no dynamic relocation remain. +// PIE: : +// PIE: {{[0-9a-f]+}}: 48 c7 c1 {{.*}} +// PIERELOC-NOT: R_X86_64_TPOFF64 static __thread int a = 10; int foo(void) { return a; } diff --git a/test/x86_64/linux/TLSIERelaxation/Inputs/tls_ie_add.s b/test/x86_64/linux/TLSIERelaxation/Inputs/tls_ie_add.s new file mode 100644 index 000000000..66e08a64b --- /dev/null +++ b/test/x86_64/linux/TLSIERelaxation/Inputs/tls_ie_add.s @@ -0,0 +1,11 @@ + .globl tls_var + .section .tbss,"awT",@nobits + .align 4 +tls_var: + .long 0 + + .text + .globl get_tls +get_tls: + addq tls_var@GOTTPOFF(%rip), %rax + ret diff --git a/test/x86_64/linux/TLSIERelaxation/Inputs/tls_ie_add_rsp.s b/test/x86_64/linux/TLSIERelaxation/Inputs/tls_ie_add_rsp.s new file mode 100644 index 000000000..c30542049 --- /dev/null +++ b/test/x86_64/linux/TLSIERelaxation/Inputs/tls_ie_add_rsp.s @@ -0,0 +1,11 @@ + .globl tls_var + .section .tbss,"awT",@nobits + .align 4 +tls_var: + .long 0 + + .text + .globl get_tls +get_tls: + addq tls_var@GOTTPOFF(%rip), %rsp + ret diff --git a/test/x86_64/linux/TLSIERelaxation/Inputs/tls_ie_addend_nonm4.s b/test/x86_64/linux/TLSIERelaxation/Inputs/tls_ie_addend_nonm4.s new file mode 100644 index 000000000..1b7b51b6c --- /dev/null +++ b/test/x86_64/linux/TLSIERelaxation/Inputs/tls_ie_addend_nonm4.s @@ -0,0 +1,15 @@ + # GOTTPOFF with addend 0 (non-standard). GNU as always emits addend -4 + # for GOTTPOFF; use .reloc to inject addend 0 explicitly. The linker + # must keep the GOT slot when the addend is not -4. + .globl tls_var_bad + .hidden tls_var_bad + .section .tbss,"awT",@nobits +tls_var_bad: .long 0 + + .text + .globl get_tls_bad + .type get_tls_bad,@function +get_tls_bad: + movq 0(%rip), %rax + .reloc .-4, R_X86_64_GOTTPOFF, tls_var_bad+0 + ret diff --git a/test/x86_64/linux/TLSIERelaxation/Inputs/tls_ie_hidden.s b/test/x86_64/linux/TLSIERelaxation/Inputs/tls_ie_hidden.s new file mode 100644 index 000000000..e005192bb --- /dev/null +++ b/test/x86_64/linux/TLSIERelaxation/Inputs/tls_ie_hidden.s @@ -0,0 +1,12 @@ + .globl tls_hidden + .hidden tls_hidden + .section .tbss,"awT",@nobits + .align 4 +tls_hidden: + .long 0 + + .text + .globl get_tls +get_tls: + movq tls_hidden@GOTTPOFF(%rip), %rax + ret diff --git a/test/x86_64/linux/TLSIERelaxation/Inputs/tls_ie_mov.s b/test/x86_64/linux/TLSIERelaxation/Inputs/tls_ie_mov.s new file mode 100644 index 000000000..b1121c7b3 --- /dev/null +++ b/test/x86_64/linux/TLSIERelaxation/Inputs/tls_ie_mov.s @@ -0,0 +1,11 @@ + .globl tls_var + .section .tbss,"awT",@nobits + .align 4 +tls_var: + .long 0 + + .text + .globl get_tls +get_tls: + movq tls_var@GOTTPOFF(%rip), %rax + ret diff --git a/test/x86_64/linux/TLSIERelaxation/TLSIERelax_AddendNonM4.test b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_AddendNonM4.test new file mode 100644 index 000000000..8f24a7a29 --- /dev/null +++ b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_AddendNonM4.test @@ -0,0 +1,14 @@ +#--TLSIERelax_AddendNonM4.test----------Static Executable--------# +# R_X86_64_GOTTPOFF with addend != -4 must not be relaxed. +# GNU as always emits GOTTPOFF with addend -4; a non-standard addend means +# the instruction is not a plain movq/addq gottpoff(%rip) load and cannot +# be rewritten. The GOT slot must be preserved. +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/tls_ie_addend_nonm4.s -o %t.o +RUN: %link %linkopts -static -e get_tls_bad -o %t.eld %t.o +RUN: %objdump -d %t.eld | %filecheck %s +#END_TEST + +# Instruction must remain a movq from GOT (8b opcode), not a movq $imm (c7). +CHECK: : +CHECK-NEXT: {{[0-9a-f]+}}: 48 8b {{.*}} diff --git a/test/x86_64/linux/TLSIERelaxation/TLSIERelax_ExecNonPreemptible.test b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_ExecNonPreemptible.test new file mode 100644 index 000000000..6c0bd3265 --- /dev/null +++ b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_ExecNonPreemptible.test @@ -0,0 +1,16 @@ +#--TLSIERelax_ExecNonPreemptible.test----------PIE Executable--------# +# R_X86_64_GOTTPOFF IE→LE relaxation: -pie link, hidden global symbol. +# Hidden symbols are non-preemptible in all link types; the relaxation must +# fire and no R_X86_64_TPOFF64 dynamic reloc should be emitted. +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/tls_ie_hidden.s -o %t.o +RUN: %link %linkopts -pie -e get_tls -o %t.eld %t.o --defsym __libc_start_main=0 +RUN: %objdump -d %t.eld | %filecheck %s +RUN: %readelf -r %t.eld | %filecheck %s --check-prefix=NORELOC +#END_TEST + +# movq $imm32, %rax encoding +CHECK: {{[0-9a-f]+}}: 48 c7 c0 {{.*}} + +# No TPOFF64 dynamic reloc should be present after relaxation. +NORELOC-NOT: R_X86_64_TPOFF64 diff --git a/test/x86_64/linux/TLSIERelaxation/TLSIERelax_PartialRelink.test b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_PartialRelink.test new file mode 100644 index 000000000..a8473e7b8 --- /dev/null +++ b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_PartialRelink.test @@ -0,0 +1,29 @@ +#--TLSIERelax_PartialRelink.test----------Static Executable--------# +# Two-stage link: a partial link (-r) must NOT relax GOTTPOFF (it has no final +# layout), and must preserve the relocation so the final link can relax it. +# Relinking the partial output into a static executable must then relax. +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/tls_ie_mov.s -o %t.o + +# Stage 1: partial link (-r). Relaxation must be skipped; the +# GOTTPOFF relocation and the GOT-indirect movq (opcode 48 8b) must survive. +RUN: %link %linkopts -r -o %t.partial.o %t.o +RUN: %readelf -r %t.partial.o | %filecheck %s --check-prefix=PARTIAL +RUN: %objdump -dr %t.partial.o | %filecheck %s --check-prefix=PARTIALDIS + +# Stage 2: relink the partial output into a final static executable. +# The GOTTPOFF must now relax to movq $imm (opcode 48 c7 c0). +RUN: %link %linkopts -static -e get_tls -o %t.eld %t.partial.o +RUN: %objdump -d %t.eld | %filecheck %s --check-prefix=FINAL +#END_TEST + +# Partial output must keep the GOTTPOFF relocation. +PARTIAL: R_X86_64_GOTTPOFF {{.*}} tls_var + +# Partial output is not relaxed: still a GOT-indirect movq (48 8b 05). +PARTIALDIS: : +PARTIALDIS-NEXT: {{[0-9a-f]+}}: 48 8b 05 {{.*}} + +# Final output: relaxed to movq $imm (48 c7 c0). +FINAL: : +FINAL-NEXT: {{[0-9a-f]+}}: 48 c7 c0 {{.*}} diff --git a/test/x86_64/linux/TLSIERelaxation/TLSIERelax_SharedKeepsGOT.test b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_SharedKeepsGOT.test new file mode 100644 index 000000000..a071df3d2 --- /dev/null +++ b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_SharedKeepsGOT.test @@ -0,0 +1,16 @@ +#--TLSIERelax_SharedKeepsGOT.test----------Shared Library--------# +# R_X86_64_GOTTPOFF must NOT be relaxed in a shared library link. +# The dynamic loader must resolve the TLS offset at load time, so the GOT +# slot and R_X86_64_TPOFF64 relocation must be preserved. +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/tls_ie_mov.s -o %t.o +RUN: %link %linkopts -shared -o %t.so %t.o +RUN: %objdump -d %t.so | %filecheck %s +RUN: %readelf -r %t.so | %filecheck %s --check-prefix=DYNRELOC +#END_TEST + +# Original movq gottpoff(%rip), %rax encoding must remain (48 8b 05). +CHECK: {{[0-9a-f]+}}: 48 8b 05 {{.*}} + +# The TPOFF64 dynamic reloc that populates the GOT at load time must be present. +DYNRELOC: R_X86_64_TPOFF64 diff --git a/test/x86_64/linux/TLSIERelaxation/TLSIERelax_StaticAdd.test b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_StaticAdd.test new file mode 100644 index 000000000..c76f8e8d0 --- /dev/null +++ b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_StaticAdd.test @@ -0,0 +1,13 @@ +#--TLSIERelax_StaticAdd.test----------Static Executable--------# +# R_X86_64_GOTTPOFF IE→LE relaxation: static link, addq sub-case. +# addq tls_var@gottpoff(%rip), %rax → leaq tpoff(%rax), %rax +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/tls_ie_add.s -o %t.o +RUN: %link %linkopts -static -e get_tls -o %t.eld %t.o +RUN: %objdump -d %t.eld | %filecheck %s +#END_TEST + +# leaq disp32(%rax), %rax is encoded as 48 8d 80 +# tls_var is the only TLS var (4 bytes) so tpoff = 0 - 4 = -4 = 0xfffffffc +CHECK: : +CHECK-NEXT: {{[0-9a-f]+}}: 48 8d 80 fc ff ff ff {{.*}}leaq diff --git a/test/x86_64/linux/TLSIERelaxation/TLSIERelax_StaticAddRsp.test b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_StaticAddRsp.test new file mode 100644 index 000000000..98bf1f56f --- /dev/null +++ b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_StaticAddRsp.test @@ -0,0 +1,15 @@ +#--TLSIERelax_StaticAddRsp.test----------Static Executable--------# +# R_X86_64_GOTTPOFF IE→LE relaxation: static link, addq %rsp sub-case. +# addq tls_var@gottpoff(%rip), %rsp → addq $tpoff, %rsp +# %rsp (and %r12) cannot use leaq: their ModR/M (0x25) would require a SIB +# byte, making leaq 8 bytes and breaking the size-preserving rewrite. So the +# addq $imm form (48 81 c4) is used instead of leaq (48 8d 80). +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/tls_ie_add_rsp.s -o %t.o +RUN: %link %linkopts -static -e get_tls -o %t.eld %t.o +RUN: %objdump -d %t.eld | %filecheck %s +#END_TEST + +# addq $imm32, %rsp is encoded as 48 81 c4 (not leaq, not a GOT load). +CHECK: : +CHECK-NEXT: {{[0-9a-f]+}}: 48 81 c4 fc ff ff ff {{.*}}addq diff --git a/test/x86_64/linux/TLSIERelaxation/TLSIERelax_StaticMov.test b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_StaticMov.test new file mode 100644 index 000000000..79ea78dab --- /dev/null +++ b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_StaticMov.test @@ -0,0 +1,18 @@ +#--TLSIERelax_StaticMov.test----------Static Executable--------# +# R_X86_64_GOTTPOFF IE→LE relaxation: static link, movq sub-case. +# movq tls_var@gottpoff(%rip), %rax → movq $tpoff, %rax +# Also verifies that no .got section is allocated for the relaxed symbol. +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/tls_ie_mov.s -o %t.o +RUN: %link %linkopts -static -e get_tls -o %t.eld %t.o +RUN: %objdump -d %t.eld | %filecheck %s +RUN: %readelf -W -S %t.eld | %filecheck %s --check-prefix=NOGOT +#END_TEST + +# movq $imm32, %rax is encoded as 48 c7 c0 +# tls_var is the only TLS var (4 bytes) so tpoff = 0 - 4 = -4 = 0xfffffffc +CHECK: : +CHECK-NEXT: {{[0-9a-f]+}}: 48 c7 c0 fc ff ff ff {{.*}}movq + +# No .got section should appear (or it should have zero size) after relaxation. +NOGOT-NOT: .got{{[^p]}} diff --git a/test/x86_64/linux/TLSIERelaxation/TLSIERelax_Trace.test b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_Trace.test new file mode 100644 index 000000000..6b3eb60e6 --- /dev/null +++ b/test/x86_64/linux/TLSIERelaxation/TLSIERelax_Trace.test @@ -0,0 +1,10 @@ +#--TLSIERelax_Trace.test----------Static Executable--------# +# --trace=relax emits a message for each relaxed GOTTPOFF reference. +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/tls_ie_mov.s -o %t.o +RUN: %link %linkopts --trace=relax -static -e get_tls -o %t.eld %t.o 2>&1 \ +RUN: | %filecheck %s +#END_TEST + +# The relaxed GOTTPOFF reference must produce a trace message. +CHECK: Trace: relaxed GOTTPOFF (mov->movimm) for symbol 'tls_var' to TP-relative immediate in section {{.*}}+0x{{[0-9a-f]+}} file {{.*}}