From 676d48fb49ceded2fc56b21bbf2f611759fbc5e0 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sat, 1 Aug 2026 18:17:35 -0700 Subject: [PATCH] [ELF,SPARC] Handle GOTDATA relocations (#213497) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A PIC data reference forms the symbol's GOT offset with R_SPARC_GOTDATA_OP_HIX22 and R_SPARC_GOTDATA_OP_LOX10 and loads through it, with R_SPARC_GOTDATA_OP marking the load: ``` sethi %gdop_hix22(sym), %g1 xor %g1, %gdop_lox10(sym), %g1 ldx [%l7 + %g1], %g1, %gdop(sym) ``` When the symbol is neither preemptible nor absolute, optimize the load to `add %l7, %g1, %g1` over the symbol's GOT-relative address. An absolute symbol keeps the GOT load, as it can be arbitrarily far from .got and we don't want the X86_64::relaxOpt complexity. Co-authored-by: Kirill A. Korinsky Co-authored-by: LemonBoy Co-authored-by: Alex Rønne Petersen --- lld/ELF/Arch/SPARCV9.cpp | 32 ++++++++ lld/test/ELF/sparcv9-gotdata.s | 141 +++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 lld/test/ELF/sparcv9-gotdata.s diff --git a/lld/ELF/Arch/SPARCV9.cpp b/lld/ELF/Arch/SPARCV9.cpp index a82ae78c958f..c35208f027e4 100644 --- a/lld/ELF/Arch/SPARCV9.cpp +++ b/lld/ELF/Arch/SPARCV9.cpp @@ -166,6 +166,20 @@ void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs rels, expr = R_GOT_OFF; break; + // Optimize the GOT load to an add of the symbol's GOT-relative address if + // applicable. Exclude absolute symbol, which can be arbitrarily far. %l7 + // points at .got, which must be retained. + case R_SPARC_GOTDATA_OP_HIX22: + case R_SPARC_GOTDATA_OP_LOX10: + case R_SPARC_GOTDATA_OP: + if (sym.isPreemptible || isAbsolute(sym)) { + expr = R_GOT_OFF; + } else { + ctx.in.got->hasGotOffRel.store(true, std::memory_order_relaxed); + expr = R_GOTREL; + } + break; + // TLS LE relocations: case R_SPARC_TLS_LE_HIX22: case R_SPARC_TLS_LE_LOX10: @@ -322,6 +336,24 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel, // T-simm13 write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x000003ff) | 0x1C00); break; + case R_SPARC_GOTDATA_OP_HIX22: { + // V-imm22. sethi encodes the complement of a negative value, which the + // paired xor undoes, so the encodable range is 33 bits. + checkInt(ctx, loc, val, 33, rel); + uint64_t v = int64_t(val) < 0 ? ~val : val; + write32be(loc, (read32be(loc) & ~0x003fffff) | ((v >> 10) & 0x003fffff)); + break; + } + case R_SPARC_GOTDATA_OP_LOX10: + // T-simm13. Only a negative value needs the sign extension bits. + write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x000003ff) | + (int64_t(val) < 0 ? 0x1c00 : 0)); + break; + case R_SPARC_GOTDATA_OP: + // ldx [%rs1 + %rs2], %rd -> add %rs1, %rs2, %rd + if (rel.expr == R_GOTREL) + write32be(loc, (read32be(loc) & 0x3e07c01f) | 0x80000000); + break; default: llvm_unreachable("unknown relocation"); } diff --git a/lld/test/ELF/sparcv9-gotdata.s b/lld/test/ELF/sparcv9-gotdata.s new file mode 100644 index 000000000000..2a95c7ee6141 --- /dev/null +++ b/lld/test/ELF/sparcv9-gotdata.s @@ -0,0 +1,141 @@ +# REQUIRES: sparc +## Test R_SPARC_GOTDATA_OP_HIX22, R_SPARC_GOTDATA_OP_LOX10 and R_SPARC_GOTDATA_OP. +## The GOT load is optimized to an add of the symbol's GOT-relative address +## unless the symbol is preemptible or absolute. + +# RUN: rm -rf %t && split-file %s %t && cd %t +# RUN: llvm-mc -filetype=obj -triple=sparcv9 a.s -o a.o +# RUN: llvm-mc -filetype=obj -triple=sparcv9 b.s -o b.o +# RUN: llvm-mc -filetype=obj -triple=sparcv9 c.s -o c.o +# RUN: llvm-mc -filetype=obj -triple=sparcv9 ifunc.s -o ifunc.o + +# RUN: ld.lld -shared a.o c.o -o a.so +# RUN: llvm-readelf -S -r a.so | FileCheck %s --check-prefix=RELOC +# RUN: llvm-objdump -d -j .text --no-show-raw-insn --no-print-imm-hex a.so | FileCheck %s + +# RELOC: [ 8] .got PROGBITS 0000000000200390 {{[0-9a-f]+}} 000020 +# RELOC: Relocation section '.rela.dyn' {{.*}} contains 1 entries: +# RELOC: 0000000000200398 {{.*}} R_SPARC_GLOB_DAT {{.*}} foo + 0 + +## foo is preemptible, so the GOT load stays. +## .got[1] - _GLOBAL_OFFSET_TABLE_ = 0x200398 - 0x200390 = 8. +# CHECK-LABEL: <_start>: +# CHECK-NEXT: sethi 0, %l1 +# CHECK-NEXT: xor %l1, 8, %l1 +# CHECK-NEXT: ldx [%l7+%l1], %l2 +## hid0 - _GLOBAL_OFFSET_TABLE_ = 0x3003b0 - 0x200390 = 0x100020. +# CHECK-NEXT: sethi 1024, %g1 +# CHECK-NEXT: xor %g1, 32, %g1 +# CHECK-NEXT: add %l7, %g1, %g1 +## hid1 - _GLOBAL_OFFSET_TABLE_ = 0x1002ec - 0x200390 = -0x1000a4. sethi holds +## the complement, which the sign-extended xor operand undoes. +# CHECK-NEXT: sethi 1024, %o2 +# CHECK-NEXT: xor %o2, -164, %o2 +# CHECK-NEXT: add %i0, %o2, %i5 +## An undefined symbol and an absolute symbol both have an absolute address, so +## neither is optimized. +# CHECK-NEXT: sethi 0, %l1 +# CHECK-NEXT: xor %l1, 16, %l1 +# CHECK-NEXT: ldx [%l7+%l1], %l2 +# CHECK-NEXT: sethi 0, %g4 +# CHECK-NEXT: xor %g4, 24, %g4 +# CHECK-NEXT: ldx [%l7+%g4], %o3 + +## foo is not preemptible in an executable and is optimized as well. undefweak +## and absolute keep the GOT load in every mode. +# RUN: ld.lld a.o c.o -o a +# RUN: llvm-objdump -d -j .text --no-show-raw-insn --no-print-imm-hex a | FileCheck %s --check-prefix=NOPIE +# NOPIE-LABEL: <_start>: +# NOPIE-NEXT: sethi 1024, %l1 +# NOPIE-NEXT: xor %l1, -4, %l1 +# NOPIE-NEXT: add %l7, %l1, %l2 +# NOPIE: sethi 0, %l1 +# NOPIE-NEXT: xor %l1, 8, %l1 +# NOPIE-NEXT: ldx [%l7+%l1], %l2 +# NOPIE-NEXT: sethi 0, %g4 +# NOPIE-NEXT: xor %g4, 16, %g4 +# NOPIE-NEXT: ldx [%l7+%g4], %o3 + +## .got holds nothing but the header, yet must be kept: %l7 points at it. +# RUN: ld.lld -shared b.o -o b.so +# RUN: llvm-readelf -S b.so | FileCheck %s --check-prefix=HEADER +# HEADER: [ 7] .got PROGBITS 00000000002002b8 {{[0-9a-f]+}} 000008 + +# RUN: not ld.lld -shared ifunc.o 2>&1 | FileCheck %s --check-prefix=IFUNC --implicit-check-not=error: +# IFUNC: error: {{.*}}relocation R_SPARC_WDISP19 out of range + +## The optimization is decided before addresses are known, so an out-of-range +## offset is an error rather than a fallback to the GOT load. The offset is +## reported as signed, not as the complement the sethi encodes. Only hid0, in +## .data, is out of range; the other symbols stay next to .got. +# RUN: not ld.lld -shared a.o c.o --section-start=.text=0x10000 --section-start=.got=0x20000 \ +# RUN: --section-start=.data=0x200000000 2>&1 | FileCheck %s --check-prefix=ERR -DVAL=8589803520 --implicit-check-not=error: +# RUN: not ld.lld -shared a.o c.o --section-start=.data=0x10000 --section-start=.text=0x200000000 \ +# RUN: --section-start=.got=0x200020000 2>&1 | FileCheck %s --check-prefix=ERR -DVAL=-8590000128 --implicit-check-not=error: +# ERR: error: a.o:(.text+0xc): relocation R_SPARC_GOTDATA_OP_HIX22 out of range: [[VAL]] is not in [-4294967296, 4294967295]; references 'hid0' + +#--- a.s +.text +.globl _start +_start: + sethi %gdop_hix22(foo), %l1 + xor %l1, %gdop_lox10(foo), %l1 + ldx [%l7 + %l1], %l2, %gdop(foo) + + sethi %gdop_hix22(hid0), %g1 + xor %g1, %gdop_lox10(hid0), %g1 + ldx [%l7 + %g1], %g1, %gdop(hid0) + +## The add keeps rs1, rs2 and rd of the load, and ld is optimized like ldx. + sethi %gdop_hix22(hid1), %o2 + xor %o2, %gdop_lox10(hid1), %o2 + ld [%i0 + %o2], %i5, %gdop(hid1) + + sethi %gdop_hix22(undefweak), %l1 + xor %l1, %gdop_lox10(undefweak), %l1 + ldx [%l7 + %l1], %l2, %gdop(undefweak) + + sethi %gdop_hix22(absolute), %g4 + xor %g4, %gdop_lox10(absolute), %g4 + ldx [%l7 + %g4], %o3, %gdop(absolute) + +.globl foo, hid0, hid1 +.hidden hid0, hid1 +foo: +hid1: + nop + +.weak undefweak +.hidden undefweak + +.data +hid0: + .xword 0 + +#--- b.s +.globl _start +_start: + sethi %gdop_hix22(local), %l1 + xor %l1, %gdop_lox10(local), %l1 + ldx [%l7 + %l1], %l2, %gdop(local) +local: + nop + +#--- c.s +## Separate file to test %gdop_lox10(absolute) relocations in a.s +.globl absolute +.hidden absolute +absolute = 0x1234 + +#--- ifunc.s +.globl _start +_start: + sethi %gdop_hix22(ifunc), %l1 + xor %l1, %gdop_lox10(ifunc), %l1 + ldx [%l7 + %l1], %l2, %gdop(ifunc) + +.globl ifunc +.hidden ifunc +.type ifunc, @gnu_indirect_function +ifunc: + nop