[lld] [lld][ELF] Support relax R_LARCH_{ALIGN, PCALA_{HI20,LO12}, GOT_PC_{HI20,LO12}} (PR #78692)

Jinyang He via llvm-commits llvm-commits at lists.llvm.org
Tue Jan 30 05:00:22 PST 2024


================
@@ -659,6 +664,253 @@ void LoongArch::relocate(uint8_t *loc, const Relocation &rel,
   }
 }
 
+// Relax R_LARCH_PCALA_{HI20,LO12}
+// pcalau12i+addi.{d,w} to pcaddi
+static void relaxPcalaAddi(const InputSection &sec, size_t i, uint64_t loc,
+                           Relocation &r, Relocation &rl, uint32_t &remove) {
+  const Symbol &sym = *r.sym;
+  const uint32_t pca = read64le(sec.content().data() + r.offset);
+  const uint32_t adi = read64le(sec.content().data() + rl.offset);
+  const uint32_t rd = pca & 0x1f;
+  const uint32_t op_addi = config->is64 ? ADDI_D : ADDI_W;
+  const uint64_t dest = sym.getVA() + r.addend;
+  const int64_t displace = dest - loc;
+
+  if (!(dest & 0x3) && isInt<22>(displace) && rd == (adi & 0x1f) &&
+      rd == ((adi >> 5) & 0x1f) && rl.offset == r.offset + 4 &&
+      (adi & 0xffc00000) == op_addi) {
+    sec.relaxAux->relocTypes[i] = R_LARCH_PCREL20_S2;
+    sec.relaxAux->relocTypes[i + 2] = R_LARCH_RELAX;
+    sec.relaxAux->writes.push_back(PCADDI | rd);
+    remove = 4;
+  }
+}
+
+// Relax R_LARCH_GOT_PC_{HI20,LO12}
+// pcalau12i+ld.{d,w} to pcalau12i+addi.{d,w} or pcaddi
+static void relaxPcalaLd(const InputSection &sec, size_t i, uint64_t loc,
+                         Relocation &r, Relocation &rl, uint32_t &remove) {
+  const Symbol &sym = *r.sym;
+  const uint32_t pca = read64le(sec.content().data() + r.offset);
+  const uint32_t ld = read64le(sec.content().data() + rl.offset);
+  const uint32_t rd = pca & 0x1f;
+  const uint32_t op_addi = config->is64 ? ADDI_D : ADDI_W;
+  const uint32_t op_ld = config->is64 ? LD_D : LD_W;
+  const uint64_t dest = sym.getVA() + r.addend;
+  const int64_t displace = dest - loc;
+
+  if (!sym.isLocal())
+    return;
+
+  if (rd != (ld & 0x1f) || rd != ((ld >> 5) & 0x1f) ||
+      rl.offset != r.offset + 4 || (ld & 0xffc00000) != op_ld)
+    return;
+
+  if (!(dest & 0x3) && isInt<22>(displace)) {
+    sec.relaxAux->relocTypes[i] = R_LARCH_PCREL20_S2;
+    sec.relaxAux->relocTypes[i + 2] = R_LARCH_RELAX;
+    sec.relaxAux->writes.push_back(PCADDI | rd);
+    remove = 4;
+  } else {
+    sec.relaxAux->relocTypes[i] = R_LARCH_PCALA_HI20;
+    sec.relaxAux->relocTypes[i + 2] = R_LARCH_PCALA_LO12;
+    sec.relaxAux->writes.push_back(PCALAU12I | rd);
+    sec.relaxAux->writes.push_back(op_addi | (rd << 5) | rd);
+  }
+}
+
+static bool relax(InputSection &sec) {
+  const uint64_t secAddr = sec.getVA();
+  auto &aux = *sec.relaxAux;
+  bool changed = false;
+  ArrayRef<SymbolAnchor> sa = ArrayRef(aux.anchors);
+  uint64_t delta = 0;
+
+  std::fill_n(aux.relocTypes.get(), sec.relocs().size(), R_LARCH_NONE);
+  aux.writes.clear();
+  for (auto [i, r] : llvm::enumerate(sec.relocs())) {
+    const uint64_t loc = secAddr + r.offset - delta;
+    uint32_t &cur = aux.relocDeltas[i], remove = 0;
+    switch (r.type) {
+    case R_LARCH_ALIGN: {
+      const uint64_t addend =
+          r.sym->isUndefined() ? Log2_64(r.addend) + 1 : r.addend;
+      const uint64_t allBytes = (1 << (addend & 0xff)) - 4;
+      const uint64_t align = 1 << (addend & 0xff);
+      const uint64_t maxBytes = addend >> 8;
+      const uint64_t off = loc & (align - 1);
+      const uint64_t curBytes = off == 0 ? 0 : align - off;
+      // All bytes beyond the alignment boundary should be removed.
+      // If emit bytes more than max bytes to emit, remove all.
+      if (maxBytes != 0 && curBytes > maxBytes)
+        remove = allBytes;
+      else
+        remove = allBytes - curBytes;
+      assert(static_cast<int32_t>(remove) >= 0 &&
+             "R_LARCH_ALIGN needs expanding the content");
+      break;
+    }
+    case R_LARCH_PCALA_HI20:
+      if (i + 3 < sec.relocs().size() &&
+          sec.relocs()[i + 1].type == R_LARCH_RELAX &&
+          sec.relocs()[i + 2].type == R_LARCH_PCALA_LO12 &&
+          sec.relocs()[i + 3].type == R_LARCH_RELAX)
+        relaxPcalaAddi(sec, i, loc, r, sec.relocs()[i + 2], remove);
+      break;
+    case R_LARCH_GOT_PC_HI20:
+      if (i + 3 < sec.relocs().size() &&
+          sec.relocs()[i + 1].type == R_LARCH_RELAX &&
+          sec.relocs()[i + 2].type == R_LARCH_GOT_PC_LO12 &&
+          sec.relocs()[i + 3].type == R_LARCH_RELAX)
+        relaxPcalaLd(sec, i, loc, r, sec.relocs()[i + 2], remove);
+      break;
+    }
+
+    // For all anchors whose offsets are <= r.offset, they are preceded by
+    // the previous relocation whose `relocDeltas` value equals `delta`.
+    // Decrease their st_value and update their st_size.
+    for (; sa.size() && sa[0].offset <= r.offset; sa = sa.slice(1)) {
+      if (sa[0].end)
+        sa[0].d->size = sa[0].offset - delta - sa[0].d->value;
+      else
+        sa[0].d->value = sa[0].offset - delta;
+    }
+    delta += remove;
+    if (delta != cur) {
+      cur = delta;
+      changed = true;
+    }
+  }
+
+  for (const SymbolAnchor &a : sa) {
+    if (a.end)
+      a.d->size = a.offset - delta - a.d->value;
+    else
+      a.d->value = a.offset - delta;
+  }
+  // Inform assignAddresses that the size has changed.
+  if (!isUInt<32>(delta))
+    fatal("section size decrease is too large: " + Twine(delta));
+  sec.bytesDropped = delta;
+  return changed;
+}
+
+// When relaxing just R_LARCH_ALIGN, relocDeltas is usually changed only once in
+// the absence of a linker script. For call and load/store R_LARCH_RELAX, code
+// shrinkage may reduce displacement and make more relocations eligible for
+// relaxation. Code shrinkage may increase displacement to a call/load/store
+// target at a higher fixed address, invalidating an earlier relaxation. Any
+// change in section sizes can have cascading effect and require another
+// relaxation pass.
+bool LoongArch::relaxOnce(int pass) const {
+  if (config->relocatable)
+    return false;
+
+  // TODO Relax multiple times.
----------------
MQ-mengqing wrote:

ld.bfd relax R_LARCH_ALIGN at the final pass. So it not conflict in this test. But it also has some shortages. Some symbol values will change due to final relax R_LARCH_ALIGN, so that the previous relax may fail.
```
.text
.p2align 4
.irp t,0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f
  .p2align 10
.endr
.p2align 6
.p2align 4
la.pcrel $s9, .L1

.section .text2, "ax"
.p2align 4
la.pcrel $r0, .L1
.data
.L1: .word 0

$ ./gas/as-new 1.s -o 1.o
$ ./ld/ld-new 1.o -e 0 -Ttext=0x100000 -Tdata=0x300000
./ld/ld-new: 1.o: relocation R_LARCH_PCREL20_S2 overflow 0x200000
```
In my idea, I think the relax method MaskRay supported for RISCV is better. I keep here just one time relaxation because I'm also afraid that I don't know enough this method. In this relaxation all R_LARCH_ALIGN will be relaxed, which is expected.

https://github.com/llvm/llvm-project/pull/78692


More information about the llvm-commits mailing list