[X86][ELF][LLD] Add R_X86_64_PCNEXT32 relocation and MO_PCNEXT32 flag for code prefetching.#211903
[X86][ELF][LLD] Add R_X86_64_PCNEXT32 relocation and MO_PCNEXT32 flag for code prefetching.#211903rlavaee wants to merge 1 commit into
Conversation
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
e7eb43f to
8a03e0f
Compare
… for code prefetching This adds toolchain infrastructure support for the proposed R_X86_64_PCNEXT32 relocation and the @PCNEXT32 (MO_PCNEXT32) target operand flag in LLVM (MC, assembler, object writer) and LLD, along with comprehensive testing and assembly extension documentation. Note: Modifying the code prefetch insertion logic (in X86InstrInfo.cpp) to actually emit @PCNEXT32 and eliminate weak fallback symbol definitions is deferred to a follow-up PR. This commit exclusively introduces the underlying toolchain mechanisms, syntax, and linker relocation resolution. x86-64 psABI proposal discussion: https://groups.google.com/g/x86-64-abi/c/v_alXxMCTgI ## Relocation Behavior & Calculation | Symbol State | Calculation | Notes | | ------------ | ----------- | ----- | | Defined | S + A - P | Identical to R_X86_64_PC32 | | Undefined | 0 | No diagnostic error reported | A 0 PC-relative displacement targets the instruction immediately following the relocated field. Consequently, an undefined reference degrades cleanly to a harmless, position-independent reference to the next instruction (a valid instruction boundary). ## Motivation LLVM's Code-prefetch insertion (llvm#166324) inserts prefetchit1 target(%rip) ahead of an anticipated instruction cache or front-end miss: prefetchit1 __llvm_prefetch_target_<fn>_<bbid>_<cs>(%rip) The target is an address inside a callee (often in another translation unit), so the compiler synthesizes a target symbol at that address and uses a PC-relative reference resolved by the linker. However, the referenced target symbol may be undefined in the final build if the profile's <function, bb_id, callsite_index> triple fails to match the final image (due to function renaming, block structure changes, or altered callsite counts), or if the target is simply not linked in. Previously, avoiding an undefined-symbol error required emitting a weak fallback definition immediately after the prefetch: prefetchit1 __llvm_prefetch_target_foo_x_y(%rip) .weak __llvm_prefetch_target_foo_x_y __llvm_prefetch_target_foo_x_y: While this allows standard ELF resolution without linker modifications (a real STB_GLOBAL overrides the weak fallback, and an absent target degrades to the fallback immediately following the instruction), it prevents the prefetch target itself from being a weak definition—the linker may mistakenly resolve against an undesired weak fallback rather than the desired weak target in another translation unit. With R_X86_64_PCNEXT32, undefined targets resolve directly to displacement 0 (pointing to the next instruction without linker errors), removing the need for fallback definitions and allowing weak target definitions to function correctly.
|
@llvm/pr-subscribers-lld-elf @llvm/pr-subscribers-lld Author: Rahman Lavaee (rlavaee) ChangesThis adds support for the proposed R_X86_64_PCNEXT32 relocation and the @PCNEXT32 (MO_PCNEXT32) target operand flag in LLVM and LLD, including MC and LLD test coverage. x86-64 psABI proposal discussion: https://groups.google.com/g/x86-64-abi/c/v_alXxMCTgI Relocation Behavior & Calculation
A 0 PC-relative displacement targets the instruction immediately following the relocated field. Consequently, an undefined reference degrades cleanly to a harmless, position-independent reference to the next instruction (a valid instruction boundary). MotivationLLVM's Code-prefetch insertion (llvm/llvm-project#166324) inserts prefetchit1 target(%rip) ahead of an anticipated instruction cache or front-end miss: The target is an address inside a callee (often in another translation unit), so the compiler synthesizes a target symbol at that address and uses a PC-relative reference resolved by the linker. However, the referenced target symbol may be undefined in the final build if the profile's <function, bb_id, callsite_index> triple fails to match the final image (due to function renaming, block structure changes, or altered callsite counts), or if the target is simply not linked in. Previously, avoiding an undefined-symbol error required emitting a weak fallback definition immediately after the prefetch: While this allows standard ELF resolution without linker modifications (a real STB_GLOBAL overrides the weak fallback, and an absent target degrades to the fallback immediately following the instruction), it prevents the prefetch target itself from being a weak definition—the linker may mistakenly resolve against an undesired weak fallback rather than the desired weak target in another translation unit. With R_X86_64_PCNEXT32, undefined targets resolve directly to displacement 0 (pointing to the next instruction without linker errors), removing the need for fallback definitions and allowing weak target definitions to function correctly. Full diff: https://github.com/llvm/llvm-project/pull/211903.diff 14 Files Affected:
diff --git a/lld/ELF/Arch/X86_64.cpp b/lld/ELF/Arch/X86_64.cpp
index fee33b03ffa6a..6c8ea2e306d93 100644
--- a/lld/ELF/Arch/X86_64.cpp
+++ b/lld/ELF/Arch/X86_64.cpp
@@ -576,6 +576,8 @@ RelExpr X86_64::getRelExpr(RelType type, const Symbol &s,
case R_X86_64_PC32:
case R_X86_64_PC64:
return R_PC;
+ case R_X86_64_PCNEXT32:
+ return RE_X86_64_PCNEXT32;
case R_X86_64_GOTOFF64:
return R_GOTPLTREL;
case R_X86_64_GOTPC32:
@@ -654,7 +656,7 @@ void X86_64::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
Symbol &sym = sec.getFile<ELFT>()->getSymbol(symIdx);
uint64_t offset = rel.r_offset;
RelType type = rel.getType(false);
- if (sym.isUndefined() && symIdx != 0 &&
+ if (sym.isUndefined() && symIdx != 0 && type != R_X86_64_PCNEXT32 &&
rs.maybeReportUndefined(cast<Undefined>(sym), offset))
continue;
int64_t addend = rs.getAddend<ELFT>(rel, type);
@@ -682,6 +684,9 @@ void X86_64::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
case R_X86_64_PC64:
rs.processR_PC(type, offset, addend, sym);
continue;
+ case R_X86_64_PCNEXT32:
+ rs.process(RE_X86_64_PCNEXT32, type, offset, sym, addend);
+ continue;
// GOT-generating relocations:
case R_X86_64_GOTPC32:
@@ -1182,6 +1187,7 @@ void X86_64::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
case R_X86_64_GOTPC32:
case R_X86_64_GOTPCREL:
case R_X86_64_PC32:
+ case R_X86_64_PCNEXT32:
case R_X86_64_PLT32:
case R_X86_64_DTPOFF32:
case R_X86_64_SIZE32:
diff --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp
index c78800787d27e..1ba83f83f10ac 100644
--- a/lld/ELF/InputSection.cpp
+++ b/lld/ELF/InputSection.cpp
@@ -921,6 +921,10 @@ uint64_t InputSectionBase::getRelocTargetVA(Ctx &ctx, const Relocation &r,
}
case RE_LOONGARCH_PAGE_PC:
return getLoongArchPageDelta(r.sym->getVA(ctx, a), p, r.type);
+ case RE_X86_64_PCNEXT32:
+ if (r.sym->isUndefined() || r.sym->isShared())
+ return 0;
+ return r.sym->getVA(ctx, a) - p;
case R_PC:
case RE_ARM_PCA: {
uint64_t dest;
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index 4702d941d28ca..0024dcbb930ed 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -846,7 +846,7 @@ bool RelocScan::isStaticLinkTimeConstant(RelExpr e, RelType type,
R_GOTPLTONLY_PC, R_PLT_PC, R_PLT_GOTREL, R_PLT_GOTPLT,
R_GOTPLT_GOTREL, R_GOTPLT_PC, RE_PPC32_PLTREL, RE_PPC64_CALL_PLT,
RE_RISCV_ADD, RE_AARCH64_GOT_PAGE, RE_LOONGARCH_PLT_PAGE_PC,
- RE_LOONGARCH_GOT, RE_LOONGARCH_GOT_PAGE_PC>(e))
+ RE_LOONGARCH_GOT, RE_LOONGARCH_GOT_PAGE_PC, RE_X86_64_PCNEXT32>(e))
return true;
// These never do, except if the entire file is position dependent or if
diff --git a/lld/ELF/Relocations.h b/lld/ELF/Relocations.h
index abc62bf01a421..bc26b5f5b8efa 100644
--- a/lld/ELF/Relocations.h
+++ b/lld/ELF/Relocations.h
@@ -126,6 +126,7 @@ enum RelExpr {
RE_LOONGARCH_PC_INDIRECT,
RE_LOONGARCH_TLSGD_PAGE_PC,
RE_LOONGARCH_TLSDESC_PAGE_PC,
+ RE_X86_64_PCNEXT32,
};
// Architecture-neutral representation of relocation.
diff --git a/lld/test/ELF/x86-64-reloc-pcnext32.s b/lld/test/ELF/x86-64-reloc-pcnext32.s
new file mode 100644
index 0000000000000..c985bc51c6f8b
--- /dev/null
+++ b/lld/test/ELF/x86-64-reloc-pcnext32.s
@@ -0,0 +1,37 @@
+# REQUIRES: x86
+# RUN: rm -rf %t && split-file %s %t
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/so.s -o %t/so.o
+# RUN: ld.lld -shared %t/so.o -o %t/libso.so
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/main.s -o %t/main.o
+# RUN: ld.lld --image-base=0x1000 --section-start=.text=0x1000 %t/main.o %t/libso.so -o %t/out
+# RUN: llvm-readelf -x .text %t/out | FileCheck %s
+
+# CHECK: Hex dump of section '.text':
+# CHECK-NEXT: 0x00001000 0c000000 00000000 00000000 c3
+
+#--- so.s
+.global shared_sym
+shared_sym:
+ ret
+
+#--- main.s
+.globl _start
+_start:
+ # R_X86_64_PCNEXT32 to defined local symbol at 0x100c (place is 0x1000, 0x100c - 0x1000 = 0x0c)
+ .reloc ., R_X86_64_PCNEXT32, local_sym
+ .byte 0x00, 0x00, 0x00, 0x00
+
+ # R_X86_64_PCNEXT32 to shared symbol (resolves to 0)
+ .reloc ., R_X86_64_PCNEXT32, shared_sym
+ .byte 0x00, 0x00, 0x00, 0x00
+
+ # R_X86_64_PCNEXT32 to undefined weak symbol (resolves to 0)
+ .reloc ., R_X86_64_PCNEXT32, undef_sym
+ .byte 0x00, 0x00, 0x00, 0x00
+
+local_sym:
+ ret
+
+.weak undef_sym
diff --git a/llvm/docs/Extensions.md b/llvm/docs/Extensions.md
index 2906b4c3f1b78..15d8b5f9688d4 100644
--- a/llvm/docs/Extensions.md
+++ b/llvm/docs/Extensions.md
@@ -764,6 +764,17 @@ is also not the same as `cmpb $foo, %dil`, which is an 8-bit comparison.
the assembler emits an `R_X86_64_GOTPCREL` relocation instead of a relaxable
`R_X86_64[_REX]_GOTPCRELX` relocation.
+
+**@PCNEXT32** can be used on symbol references to indicate that the immediate
+operand is an offset to the target symbol from the next code location, causing
+the assembler to emit an `R_X86_64_PCNEXT32` relocation. If the target symbol
+is defined, the relocation resolves identically to `R_X86_64_PC32` (`S + A - P`).
+If the target symbol is undefined or in a shared library, it resolves to zero
+displacement (`0`) without generating a linker diagnostic. This allows
+undefined code prefetch targets (e.g. `prefetchit1 target@PCNEXT32(%rip)`) to
+gracefully degrade to a harmless prefetch of the immediately following
+instruction.
+
### Windows on ARM
#### Stack Probe Emission
diff --git a/llvm/include/llvm/BinaryFormat/ELFRelocs/x86_64.def b/llvm/include/llvm/BinaryFormat/ELFRelocs/x86_64.def
index a93d92870cf32..2f23f52fa12b9 100644
--- a/llvm/include/llvm/BinaryFormat/ELFRelocs/x86_64.def
+++ b/llvm/include/llvm/BinaryFormat/ELFRelocs/x86_64.def
@@ -46,4 +46,5 @@ ELF_RELOC(R_X86_64_REX_GOTPCRELX, 42)
ELF_RELOC(R_X86_64_CODE_4_GOTPCRELX, 43)
ELF_RELOC(R_X86_64_CODE_4_GOTTPOFF, 44)
ELF_RELOC(R_X86_64_CODE_4_GOTPC32_TLSDESC, 45)
+ELF_RELOC(R_X86_64_PCNEXT32, 46)
ELF_RELOC(R_X86_64_CODE_6_GOTTPOFF, 50)
diff --git a/llvm/lib/Target/X86/MCTargetDesc/X86BaseInfo.h b/llvm/lib/Target/X86/MCTargetDesc/X86BaseInfo.h
index 06b13c0982d21..bab90e598bb0c 100644
--- a/llvm/lib/Target/X86/MCTargetDesc/X86BaseInfo.h
+++ b/llvm/lib/Target/X86/MCTargetDesc/X86BaseInfo.h
@@ -394,6 +394,11 @@ enum TOF {
/// See the X86-64 ELF ABI supplement for more details.
/// SYMBOL_LABEL @PLT
MO_PLT,
+ /// MO_PCNEXT32 - On a symbol operand this indicates that the immediate is
+ /// offset to the prefetch target symbol name from the next code location.
+ /// If the target symbol is undefined, it will resolve to zero displacement.
+ /// SYMBOL_LABEL @PCNEXT32
+ MO_PCNEXT32,
/// MO_TLSGD - On a symbol operand this indicates that the immediate is
/// the offset of the GOT entry with the TLS index structure that contains
/// the module number and variable offset for the symbol. Used in the
diff --git a/llvm/lib/Target/X86/MCTargetDesc/X86ELFObjectWriter.cpp b/llvm/lib/Target/X86/MCTargetDesc/X86ELFObjectWriter.cpp
index c6297115542e5..539316194c0aa 100644
--- a/llvm/lib/Target/X86/MCTargetDesc/X86ELFObjectWriter.cpp
+++ b/llvm/lib/Target/X86/MCTargetDesc/X86ELFObjectWriter.cpp
@@ -212,6 +212,9 @@ unsigned X86ELFObjectWriter::getRelocType64(SMLoc Loc, X86::Specifier Specifier,
case X86::S_PLT:
checkIs32(Loc, Type);
return ELF::R_X86_64_PLT32;
+ case X86::S_PCNEXT32:
+ checkIs32(Loc, Type);
+ return ELF::R_X86_64_PCNEXT32;
case X86::S_GOTPCREL:
checkIs32(Loc, Type);
// Older versions of ld.bfd/ld.gold/lld
diff --git a/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp b/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp
index adb7bd2e58f96..de03d865b1415 100644
--- a/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp
+++ b/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp
@@ -49,6 +49,7 @@ const MCAsmInfo::AtSpecifier atSpecifiers[] = {
{X86::S_INDNTPOFF, "INDNTPOFF"},
{MCSymbolRefExpr::VK_COFF_IMGREL32, "IMGREL"},
{X86::S_NTPOFF, "NTPOFF"},
+ {X86::S_PCNEXT32, "PCNEXT32"},
{X86::S_PCREL, "PCREL"},
{X86::S_PLT, "PLT"},
{X86::S_PLTOFF, "PLTOFF"},
diff --git a/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.h b/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.h
index a0bc7ed1f5802..c0939dec97601 100644
--- a/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.h
+++ b/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.h
@@ -94,6 +94,7 @@ enum {
S_GOTTPOFF,
S_INDNTPOFF,
S_NTPOFF,
+ S_PCNEXT32,
S_PCREL,
S_PLT,
S_PLTOFF,
diff --git a/llvm/lib/Target/X86/X86AsmPrinter.cpp b/llvm/lib/Target/X86/X86AsmPrinter.cpp
index f22b89b9b6e96..545157a93937c 100644
--- a/llvm/lib/Target/X86/X86AsmPrinter.cpp
+++ b/llvm/lib/Target/X86/X86AsmPrinter.cpp
@@ -332,6 +332,9 @@ void X86AsmPrinter::PrintSymbolOperand(const MachineOperand &MO,
case X86II::MO_GOT: O << "@GOT"; break;
case X86II::MO_GOTOFF: O << "@GOTOFF"; break;
case X86II::MO_PLT: O << "@PLT"; break;
+ case X86II::MO_PCNEXT32:
+ O << "@PCNEXT32";
+ break;
case X86II::MO_TLVP: O << "@TLVP"; break;
case X86II::MO_TLVP_PIC_BASE:
O << "@TLVP" << '-';
diff --git a/llvm/lib/Target/X86/X86MCInstLower.cpp b/llvm/lib/Target/X86/X86MCInstLower.cpp
index ad0946b4c3310..feb11cf70c129 100644
--- a/llvm/lib/Target/X86/X86MCInstLower.cpp
+++ b/llvm/lib/Target/X86/X86MCInstLower.cpp
@@ -301,6 +301,9 @@ MCOperand X86MCInstLower::LowerSymbolOperand(const MachineOperand &MO,
case X86II::MO_PLT:
Specifier = X86::S_PLT;
break;
+ case X86II::MO_PCNEXT32:
+ Specifier = X86::S_PCNEXT32;
+ break;
case X86II::MO_ABS8:
Specifier = X86::S_ABS8;
break;
diff --git a/llvm/test/MC/X86/elf-reloc-pcnext32.s b/llvm/test/MC/X86/elf-reloc-pcnext32.s
new file mode 100644
index 0000000000000..94bef30ecefde
--- /dev/null
+++ b/llvm/test/MC/X86/elf-reloc-pcnext32.s
@@ -0,0 +1,11 @@
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %s | llvm-readobj -r - | FileCheck %s
+
+# CHECK: Section ({{.*}}) .rela.text {
+# CHECK-NEXT: 0x3 R_X86_64_PCNEXT32 target 0xFFFFFFFFFFFFFFFC
+# CHECK-NEXT: 0x7 R_X86_64_PCNEXT32 target 0x0
+# CHECK-NEXT: }
+
+prefetchit1 target@PCNEXT32(%rip)
+
+.reloc ., R_X86_64_PCNEXT32, target
+.long 0
|
|
@llvm/pr-subscribers-backend-x86 Author: Rahman Lavaee (rlavaee) ChangesThis adds support for the proposed R_X86_64_PCNEXT32 relocation and the @PCNEXT32 (MO_PCNEXT32) target operand flag in LLVM and LLD, including MC and LLD test coverage. x86-64 psABI proposal discussion: https://groups.google.com/g/x86-64-abi/c/v_alXxMCTgI Relocation Behavior & Calculation
A 0 PC-relative displacement targets the instruction immediately following the relocated field. Consequently, an undefined reference degrades cleanly to a harmless, position-independent reference to the next instruction (a valid instruction boundary). MotivationLLVM's Code-prefetch insertion (llvm/llvm-project#166324) inserts prefetchit1 target(%rip) ahead of an anticipated instruction cache or front-end miss: The target is an address inside a callee (often in another translation unit), so the compiler synthesizes a target symbol at that address and uses a PC-relative reference resolved by the linker. However, the referenced target symbol may be undefined in the final build if the profile's <function, bb_id, callsite_index> triple fails to match the final image (due to function renaming, block structure changes, or altered callsite counts), or if the target is simply not linked in. Previously, avoiding an undefined-symbol error required emitting a weak fallback definition immediately after the prefetch: While this allows standard ELF resolution without linker modifications (a real STB_GLOBAL overrides the weak fallback, and an absent target degrades to the fallback immediately following the instruction), it prevents the prefetch target itself from being a weak definition—the linker may mistakenly resolve against an undesired weak fallback rather than the desired weak target in another translation unit. With R_X86_64_PCNEXT32, undefined targets resolve directly to displacement 0 (pointing to the next instruction without linker errors), removing the need for fallback definitions and allowing weak target definitions to function correctly. Full diff: https://github.com/llvm/llvm-project/pull/211903.diff 14 Files Affected:
diff --git a/lld/ELF/Arch/X86_64.cpp b/lld/ELF/Arch/X86_64.cpp
index fee33b03ffa6a..6c8ea2e306d93 100644
--- a/lld/ELF/Arch/X86_64.cpp
+++ b/lld/ELF/Arch/X86_64.cpp
@@ -576,6 +576,8 @@ RelExpr X86_64::getRelExpr(RelType type, const Symbol &s,
case R_X86_64_PC32:
case R_X86_64_PC64:
return R_PC;
+ case R_X86_64_PCNEXT32:
+ return RE_X86_64_PCNEXT32;
case R_X86_64_GOTOFF64:
return R_GOTPLTREL;
case R_X86_64_GOTPC32:
@@ -654,7 +656,7 @@ void X86_64::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
Symbol &sym = sec.getFile<ELFT>()->getSymbol(symIdx);
uint64_t offset = rel.r_offset;
RelType type = rel.getType(false);
- if (sym.isUndefined() && symIdx != 0 &&
+ if (sym.isUndefined() && symIdx != 0 && type != R_X86_64_PCNEXT32 &&
rs.maybeReportUndefined(cast<Undefined>(sym), offset))
continue;
int64_t addend = rs.getAddend<ELFT>(rel, type);
@@ -682,6 +684,9 @@ void X86_64::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
case R_X86_64_PC64:
rs.processR_PC(type, offset, addend, sym);
continue;
+ case R_X86_64_PCNEXT32:
+ rs.process(RE_X86_64_PCNEXT32, type, offset, sym, addend);
+ continue;
// GOT-generating relocations:
case R_X86_64_GOTPC32:
@@ -1182,6 +1187,7 @@ void X86_64::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
case R_X86_64_GOTPC32:
case R_X86_64_GOTPCREL:
case R_X86_64_PC32:
+ case R_X86_64_PCNEXT32:
case R_X86_64_PLT32:
case R_X86_64_DTPOFF32:
case R_X86_64_SIZE32:
diff --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp
index c78800787d27e..1ba83f83f10ac 100644
--- a/lld/ELF/InputSection.cpp
+++ b/lld/ELF/InputSection.cpp
@@ -921,6 +921,10 @@ uint64_t InputSectionBase::getRelocTargetVA(Ctx &ctx, const Relocation &r,
}
case RE_LOONGARCH_PAGE_PC:
return getLoongArchPageDelta(r.sym->getVA(ctx, a), p, r.type);
+ case RE_X86_64_PCNEXT32:
+ if (r.sym->isUndefined() || r.sym->isShared())
+ return 0;
+ return r.sym->getVA(ctx, a) - p;
case R_PC:
case RE_ARM_PCA: {
uint64_t dest;
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index 4702d941d28ca..0024dcbb930ed 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -846,7 +846,7 @@ bool RelocScan::isStaticLinkTimeConstant(RelExpr e, RelType type,
R_GOTPLTONLY_PC, R_PLT_PC, R_PLT_GOTREL, R_PLT_GOTPLT,
R_GOTPLT_GOTREL, R_GOTPLT_PC, RE_PPC32_PLTREL, RE_PPC64_CALL_PLT,
RE_RISCV_ADD, RE_AARCH64_GOT_PAGE, RE_LOONGARCH_PLT_PAGE_PC,
- RE_LOONGARCH_GOT, RE_LOONGARCH_GOT_PAGE_PC>(e))
+ RE_LOONGARCH_GOT, RE_LOONGARCH_GOT_PAGE_PC, RE_X86_64_PCNEXT32>(e))
return true;
// These never do, except if the entire file is position dependent or if
diff --git a/lld/ELF/Relocations.h b/lld/ELF/Relocations.h
index abc62bf01a421..bc26b5f5b8efa 100644
--- a/lld/ELF/Relocations.h
+++ b/lld/ELF/Relocations.h
@@ -126,6 +126,7 @@ enum RelExpr {
RE_LOONGARCH_PC_INDIRECT,
RE_LOONGARCH_TLSGD_PAGE_PC,
RE_LOONGARCH_TLSDESC_PAGE_PC,
+ RE_X86_64_PCNEXT32,
};
// Architecture-neutral representation of relocation.
diff --git a/lld/test/ELF/x86-64-reloc-pcnext32.s b/lld/test/ELF/x86-64-reloc-pcnext32.s
new file mode 100644
index 0000000000000..c985bc51c6f8b
--- /dev/null
+++ b/lld/test/ELF/x86-64-reloc-pcnext32.s
@@ -0,0 +1,37 @@
+# REQUIRES: x86
+# RUN: rm -rf %t && split-file %s %t
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/so.s -o %t/so.o
+# RUN: ld.lld -shared %t/so.o -o %t/libso.so
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/main.s -o %t/main.o
+# RUN: ld.lld --image-base=0x1000 --section-start=.text=0x1000 %t/main.o %t/libso.so -o %t/out
+# RUN: llvm-readelf -x .text %t/out | FileCheck %s
+
+# CHECK: Hex dump of section '.text':
+# CHECK-NEXT: 0x00001000 0c000000 00000000 00000000 c3
+
+#--- so.s
+.global shared_sym
+shared_sym:
+ ret
+
+#--- main.s
+.globl _start
+_start:
+ # R_X86_64_PCNEXT32 to defined local symbol at 0x100c (place is 0x1000, 0x100c - 0x1000 = 0x0c)
+ .reloc ., R_X86_64_PCNEXT32, local_sym
+ .byte 0x00, 0x00, 0x00, 0x00
+
+ # R_X86_64_PCNEXT32 to shared symbol (resolves to 0)
+ .reloc ., R_X86_64_PCNEXT32, shared_sym
+ .byte 0x00, 0x00, 0x00, 0x00
+
+ # R_X86_64_PCNEXT32 to undefined weak symbol (resolves to 0)
+ .reloc ., R_X86_64_PCNEXT32, undef_sym
+ .byte 0x00, 0x00, 0x00, 0x00
+
+local_sym:
+ ret
+
+.weak undef_sym
diff --git a/llvm/docs/Extensions.md b/llvm/docs/Extensions.md
index 2906b4c3f1b78..15d8b5f9688d4 100644
--- a/llvm/docs/Extensions.md
+++ b/llvm/docs/Extensions.md
@@ -764,6 +764,17 @@ is also not the same as `cmpb $foo, %dil`, which is an 8-bit comparison.
the assembler emits an `R_X86_64_GOTPCREL` relocation instead of a relaxable
`R_X86_64[_REX]_GOTPCRELX` relocation.
+
+**@PCNEXT32** can be used on symbol references to indicate that the immediate
+operand is an offset to the target symbol from the next code location, causing
+the assembler to emit an `R_X86_64_PCNEXT32` relocation. If the target symbol
+is defined, the relocation resolves identically to `R_X86_64_PC32` (`S + A - P`).
+If the target symbol is undefined or in a shared library, it resolves to zero
+displacement (`0`) without generating a linker diagnostic. This allows
+undefined code prefetch targets (e.g. `prefetchit1 target@PCNEXT32(%rip)`) to
+gracefully degrade to a harmless prefetch of the immediately following
+instruction.
+
### Windows on ARM
#### Stack Probe Emission
diff --git a/llvm/include/llvm/BinaryFormat/ELFRelocs/x86_64.def b/llvm/include/llvm/BinaryFormat/ELFRelocs/x86_64.def
index a93d92870cf32..2f23f52fa12b9 100644
--- a/llvm/include/llvm/BinaryFormat/ELFRelocs/x86_64.def
+++ b/llvm/include/llvm/BinaryFormat/ELFRelocs/x86_64.def
@@ -46,4 +46,5 @@ ELF_RELOC(R_X86_64_REX_GOTPCRELX, 42)
ELF_RELOC(R_X86_64_CODE_4_GOTPCRELX, 43)
ELF_RELOC(R_X86_64_CODE_4_GOTTPOFF, 44)
ELF_RELOC(R_X86_64_CODE_4_GOTPC32_TLSDESC, 45)
+ELF_RELOC(R_X86_64_PCNEXT32, 46)
ELF_RELOC(R_X86_64_CODE_6_GOTTPOFF, 50)
diff --git a/llvm/lib/Target/X86/MCTargetDesc/X86BaseInfo.h b/llvm/lib/Target/X86/MCTargetDesc/X86BaseInfo.h
index 06b13c0982d21..bab90e598bb0c 100644
--- a/llvm/lib/Target/X86/MCTargetDesc/X86BaseInfo.h
+++ b/llvm/lib/Target/X86/MCTargetDesc/X86BaseInfo.h
@@ -394,6 +394,11 @@ enum TOF {
/// See the X86-64 ELF ABI supplement for more details.
/// SYMBOL_LABEL @PLT
MO_PLT,
+ /// MO_PCNEXT32 - On a symbol operand this indicates that the immediate is
+ /// offset to the prefetch target symbol name from the next code location.
+ /// If the target symbol is undefined, it will resolve to zero displacement.
+ /// SYMBOL_LABEL @PCNEXT32
+ MO_PCNEXT32,
/// MO_TLSGD - On a symbol operand this indicates that the immediate is
/// the offset of the GOT entry with the TLS index structure that contains
/// the module number and variable offset for the symbol. Used in the
diff --git a/llvm/lib/Target/X86/MCTargetDesc/X86ELFObjectWriter.cpp b/llvm/lib/Target/X86/MCTargetDesc/X86ELFObjectWriter.cpp
index c6297115542e5..539316194c0aa 100644
--- a/llvm/lib/Target/X86/MCTargetDesc/X86ELFObjectWriter.cpp
+++ b/llvm/lib/Target/X86/MCTargetDesc/X86ELFObjectWriter.cpp
@@ -212,6 +212,9 @@ unsigned X86ELFObjectWriter::getRelocType64(SMLoc Loc, X86::Specifier Specifier,
case X86::S_PLT:
checkIs32(Loc, Type);
return ELF::R_X86_64_PLT32;
+ case X86::S_PCNEXT32:
+ checkIs32(Loc, Type);
+ return ELF::R_X86_64_PCNEXT32;
case X86::S_GOTPCREL:
checkIs32(Loc, Type);
// Older versions of ld.bfd/ld.gold/lld
diff --git a/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp b/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp
index adb7bd2e58f96..de03d865b1415 100644
--- a/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp
+++ b/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp
@@ -49,6 +49,7 @@ const MCAsmInfo::AtSpecifier atSpecifiers[] = {
{X86::S_INDNTPOFF, "INDNTPOFF"},
{MCSymbolRefExpr::VK_COFF_IMGREL32, "IMGREL"},
{X86::S_NTPOFF, "NTPOFF"},
+ {X86::S_PCNEXT32, "PCNEXT32"},
{X86::S_PCREL, "PCREL"},
{X86::S_PLT, "PLT"},
{X86::S_PLTOFF, "PLTOFF"},
diff --git a/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.h b/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.h
index a0bc7ed1f5802..c0939dec97601 100644
--- a/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.h
+++ b/llvm/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.h
@@ -94,6 +94,7 @@ enum {
S_GOTTPOFF,
S_INDNTPOFF,
S_NTPOFF,
+ S_PCNEXT32,
S_PCREL,
S_PLT,
S_PLTOFF,
diff --git a/llvm/lib/Target/X86/X86AsmPrinter.cpp b/llvm/lib/Target/X86/X86AsmPrinter.cpp
index f22b89b9b6e96..545157a93937c 100644
--- a/llvm/lib/Target/X86/X86AsmPrinter.cpp
+++ b/llvm/lib/Target/X86/X86AsmPrinter.cpp
@@ -332,6 +332,9 @@ void X86AsmPrinter::PrintSymbolOperand(const MachineOperand &MO,
case X86II::MO_GOT: O << "@GOT"; break;
case X86II::MO_GOTOFF: O << "@GOTOFF"; break;
case X86II::MO_PLT: O << "@PLT"; break;
+ case X86II::MO_PCNEXT32:
+ O << "@PCNEXT32";
+ break;
case X86II::MO_TLVP: O << "@TLVP"; break;
case X86II::MO_TLVP_PIC_BASE:
O << "@TLVP" << '-';
diff --git a/llvm/lib/Target/X86/X86MCInstLower.cpp b/llvm/lib/Target/X86/X86MCInstLower.cpp
index ad0946b4c3310..feb11cf70c129 100644
--- a/llvm/lib/Target/X86/X86MCInstLower.cpp
+++ b/llvm/lib/Target/X86/X86MCInstLower.cpp
@@ -301,6 +301,9 @@ MCOperand X86MCInstLower::LowerSymbolOperand(const MachineOperand &MO,
case X86II::MO_PLT:
Specifier = X86::S_PLT;
break;
+ case X86II::MO_PCNEXT32:
+ Specifier = X86::S_PCNEXT32;
+ break;
case X86II::MO_ABS8:
Specifier = X86::S_ABS8;
break;
diff --git a/llvm/test/MC/X86/elf-reloc-pcnext32.s b/llvm/test/MC/X86/elf-reloc-pcnext32.s
new file mode 100644
index 0000000000000..94bef30ecefde
--- /dev/null
+++ b/llvm/test/MC/X86/elf-reloc-pcnext32.s
@@ -0,0 +1,11 @@
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %s | llvm-readobj -r - | FileCheck %s
+
+# CHECK: Section ({{.*}}) .rela.text {
+# CHECK-NEXT: 0x3 R_X86_64_PCNEXT32 target 0xFFFFFFFFFFFFFFFC
+# CHECK-NEXT: 0x7 R_X86_64_PCNEXT32 target 0x0
+# CHECK-NEXT: }
+
+prefetchit1 target@PCNEXT32(%rip)
+
+.reloc ., R_X86_64_PCNEXT32, target
+.long 0
|
There was a problem hiding this comment.
Thanks for putting this together. Since I'm the one who proposed this relocation on the psABI list, I'd have appreciated a ping before the implementation was written -- I was planning to do the implementation myself.
Two things before this can move forward.
First, the relocation number isn't allocated yet. I proposed this on the x86-64-abi list (https://groups.google.com/g/x86-64-abi/c/v_alXxMCTgI) and it hasn't drawn any responses so far. Until the psABI assigns a value, anything we land in LLVM risks colliding with a future allocation, so this shouldn't merge yet regardless of the implementation.
I'll open a PR against the x86-psABI repo with concrete wording to try to get it unstuck.
Second, could you split this? I'm happy to review the MC/assembler half.
For the lld/ELF half I have a prototype and I'd prefer to write it myself: as the lld/ELF maintainer, reviewing a new relocation's linker implementation end-to-end costs me more time than writing it, and there are enough non-obvious interactions that I'd rather own that surface.
Once the psABI side lands I'll send the lld patch.
Also, for future work in this area: please ping if I have raised an object file format feature (rather than commenting), so we don't duplicate effort.
|
Filed a merge request and bumped the original list thread pointing at it. https://gitlab.com/x86-psABIs/x86-64-ABI/-/merge_requests/69 This needs to merge before the LLVM side can move forward. Until a relocation number is allocated, I'd rather not review or land anything in the meantime. If you need this sooner than the psABI process allows, carrying it as a downstream patch is a reasonable option. Happy to point out anything in the current patches that would make the eventual rebase easier. |
This adds support for the proposed R_X86_64_PCNEXT32 relocation and the @PCNEXT32 (MO_PCNEXT32) target operand flag in LLVM and LLD, including MC and LLD test coverage.
x86-64 psABI proposal discussion: https://groups.google.com/g/x86-64-abi/c/v_alXxMCTgI
Relocation Behavior & Calculation
A 0 PC-relative displacement targets the instruction immediately following the relocated field. Consequently, an undefined reference degrades cleanly to a harmless, position-independent reference to the next instruction (a valid instruction boundary).
Motivation
LLVM's Code-prefetch insertion (#166324) inserts prefetchit1 target(%rip) ahead of an anticipated instruction cache or front-end miss:
The target is an address inside a callee (often in another translation unit), so the compiler synthesizes a target symbol at that address and uses a PC-relative reference resolved by the linker.
However, the referenced target symbol may be undefined in the final build if the profile's <function, bb_id, callsite_index> triple fails to match the final image (due to function renaming, block structure changes, or altered callsite counts), or if the target is simply not linked in.
Currently, avoiding an undefined-symbol error required emitting a weak fallback definition immediately after the prefetch:
While this allows standard ELF resolution without linker modifications (a real STB_GLOBAL overrides the weak fallback, and an absent target degrades to the fallback immediately following the instruction), it prevents the prefetch target itself from being a weak definition—the linker may mistakenly resolve against an undesired weak fallback rather than the desired weak target in another translation unit.
With R_X86_64_PCNEXT32, undefined targets resolve directly to displacement 0 (pointing to the next instruction without linker errors), removing the need for fallback definitions and allowing weak target definitions to function correctly.