From 2b870b1f213f2d645f4fa685371fbefea09b2969 Mon Sep 17 00:00:00 2001
From: Lu Weining <luweining@loongson.cn>
Date: Mon, 25 Dec 2023 17:40:48 +0800
Subject: [PATCH 1/6] [lld][LoongArch] Support the R_LARCH_CALL36 relocation
 type (#73346)

R_LARCH_CALL36 was designed for function call on medium code model where
the 2 instructions (pcaddu18i + jirl) must be adjacent. This is expected
to replace current medium code model implementation, i.e.
R_LARCH_PCALA_{HI20,LO12} on pcalau12i + jirl.

See https://github.com/loongson/la-abi-specs/pull/3 for more details.

(cherry picked from commit 88548df0fc08364bd03148c936e36f0bb07dde8a)
---
 lld/ELF/Arch/LoongArch.cpp      | 20 ++++++++++
 lld/test/ELF/loongarch-call36.s | 69 +++++++++++++++++++++++++++++++++
 2 files changed, 89 insertions(+)
 create mode 100644 lld/test/ELF/loongarch-call36.s

diff --git a/lld/ELF/Arch/LoongArch.cpp b/lld/ELF/Arch/LoongArch.cpp
index 160fab4aeba9..72d9c6838e31 100644
--- a/lld/ELF/Arch/LoongArch.cpp
+++ b/lld/ELF/Arch/LoongArch.cpp
@@ -479,6 +479,7 @@ RelExpr LoongArch::getRelExpr(const RelType type, const Symbol &s,
   case R_LARCH_B16:
   case R_LARCH_B21:
   case R_LARCH_B26:
+  case R_LARCH_CALL36:
     return R_PLT_PC;
   case R_LARCH_GOT_PC_HI20:
   case R_LARCH_GOT64_PC_LO20:
@@ -607,6 +608,25 @@ void LoongArch::relocate(uint8_t *loc, const Relocation &rel,
     write32le(loc, setD10k16(read32le(loc), val >> 2));
     return;
 
+  case R_LARCH_CALL36: {
+    // This relocation is designed for adjancent pcaddu18i+jirl pairs that
+    // are patched in one time. Because of sign extension of these insns'
+    // immediate fields, the relocation range is [-128G - 0x20000, +128G -
+    // 0x20000) (of course must be 4-byte aligned).
+    if (((int64_t)val + 0x20000) != llvm::SignExtend64(val + 0x20000, 38))
+      reportRangeError(loc, rel, Twine(val), llvm::minIntN(38) - 0x20000,
+                       llvm::maxIntN(38) - 0x20000);
+    checkAlignment(loc, val, 4, rel);
+    // Since jirl performs sign extension on the offset immediate, adds (1<<17)
+    // to original val to get the correct hi20.
+    uint32_t hi20 = extractBits(val + (1 << 17), 37, 18);
+    // Despite the name, the lower part is actually 18 bits with 4-byte aligned.
+    uint32_t lo16 = extractBits(val, 17, 2);
+    write32le(loc, setJ20(read32le(loc), hi20));
+    write32le(loc + 4, setK16(read32le(loc + 4), lo16));
+    return;
+  }
+
   // Relocs intended for `addi`, `ld` or `st`.
   case R_LARCH_PCALA_LO12:
     // We have to again inspect the insn word to handle the R_LARCH_PCALA_LO12
diff --git a/lld/test/ELF/loongarch-call36.s b/lld/test/ELF/loongarch-call36.s
new file mode 100644
index 000000000000..2d25a2ac64ed
--- /dev/null
+++ b/lld/test/ELF/loongarch-call36.s
@@ -0,0 +1,69 @@
+# REQUIRES: loongarch
+
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-mc --filetype=obj --triple=loongarch64-unknown-elf %t/a.s -o %t/a.o
+
+# RUN: ld.lld %t/a.o --section-start=.text=0x20010 --section-start=.sec.foo=0x60020 -o %t/exe1
+# RUN: llvm-objdump --no-show-raw-insn -d %t/exe1 | FileCheck --match-full-lines %s --check-prefix=EXE1
+## hi20 = target - pc + (1 << 17) >> 18 = 0x60020 - 0x20010 + 0x20000 >> 18 = 1
+## lo18 = target - pc & (1 << 18) - 1 = 0x60020 - 0x20010 & 0x3ffff = 16
+# EXE1:      20010: pcaddu18i $t0, 1
+# EXE1-NEXT: 20014: jirl $zero, $t0, 16
+
+# RUN: ld.lld %t/a.o --section-start=.text=0x20010 --section-start=.sec.foo=0x40020 -o %t/exe2
+# RUN: llvm-objdump --no-show-raw-insn -d %t/exe2 | FileCheck --match-full-lines %s --check-prefix=EXE2
+## hi20 = target - pc + (1 << 17) >> 18 = 0x40020 - 0x20010 + 0x20000 >> 18 = 1
+## lo18 = target - pc & (1 << 18) - 1 = 0x40020 - 0x20010 & 0x3ffff = -131056
+# EXE2:      20010: pcaddu18i $t0, 1
+# EXE2-NEXT: 20014: jirl $zero, $t0, -131056
+
+# RUN: ld.lld %t/a.o -shared -T %t/a.t -o %t/a.so
+# RUN: llvm-readelf -x .got.plt %t/a.so | FileCheck --check-prefix=GOTPLT %s
+# RUN: llvm-objdump -d --no-show-raw-insn %t/a.so | FileCheck --check-prefix=SO %s
+## PLT should be present in this case.
+# SO:    Disassembly of section .plt:
+# SO:    <.plt>:
+##       foo@plt:
+# SO:    1234520:  pcaddu12i $t3, 64{{$}}
+# SO-NEXT:         ld.d $t3, $t3, 544{{$}}
+# SO-NEXT:         jirl $t1, $t3, 0
+# SO-NEXT:         nop
+
+# SO:   Disassembly of section .text:
+# SO:   <_start>:
+## hi20 = foo@plt - pc + (1 << 17) >> 18 = 0x1234520 - 0x1274670 + 0x20000 >> 18 = -1
+## lo18 = foo@plt - pc & (1 << 18) - 1 = 0x1234520 - 0x1274670 & 0x3ffff = -336
+# SO-NEXT: pcaddu18i $t0, -1{{$}}
+# SO-NEXT: jirl $zero, $t0, -336{{$}}
+
+# GOTPLT:      section '.got.plt':
+# GOTPLT-NEXT: 0x01274730 00000000 00000000 00000000 00000000
+# GOTPLT-NEXT: 0x01274740 00452301 00000000
+
+# RUN: not ld.lld %t/a.o --section-start=.text=0x20000 --section-start=.sec.foo=0x2000020000 -o /dev/null 2>&1 | \
+# RUN:   FileCheck -DFILE=%t/a.o --check-prefix=ERROR-RANGE %s
+# ERROR-RANGE: error: [[FILE]]:(.text+0x0): relocation R_LARCH_CALL36 out of range: 137438953472 is not in [-137439084544, 137438822399]; references 'foo'
+
+## Impossible case in reality becasue all LoongArch instructions are fixed 4-bytes long.
+# RUN: not ld.lld %t/a.o --section-start=.text=0x20000 --section-start=.sec.foo=0x40001 -o /dev/null 2>&1 | \
+# RUN:   FileCheck -DFILE=%t/a.o --check-prefix=ERROR-ALIGN %s
+# ERROR-ALIGN: error: [[FILE]]:(.text+0x0): improper alignment for relocation R_LARCH_CALL36: 0x20001 is not aligned to 4 bytes
+
+#--- a.t
+SECTIONS {
+ .plt   0x1234500: { *(.plt) }
+ .text  0x1274670: { *(.text) }
+}
+
+#--- a.s
+.text
+.global _start
+_start:
+  .reloc ., R_LARCH_CALL36, foo
+  pcaddu18i $t0, 0
+  jirl      $zero, $t0, 0
+
+.section .sec.foo,"ax"
+.global foo
+foo:
+  ret
-- 
2.20.1


From 6accc3e17550f87c2e5154fdee4056e21f680542 Mon Sep 17 00:00:00 2001
From: Weining Lu <luweining@loongson.cn>
Date: Mon, 25 Dec 2023 18:28:19 +0800
Subject: [PATCH 2/6] [lld][test][LoongArch] Remove the test for R_LARCH_CALL36
 range checking

Several buildbots report:
ld.lld: error: failed to open /dev/null: Cannot allocate memory

For example:
- https://lab.llvm.org/buildbot/#/builders/184/builds/8842
- https://lab.llvm.org/buildbot/#/builders/247/builds/12559

(cherry picked from commit 0fbc728dba97149e530cfb7f2ada0283c398a7ce)
---
 lld/test/ELF/loongarch-call36.s | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/lld/test/ELF/loongarch-call36.s b/lld/test/ELF/loongarch-call36.s
index 2d25a2ac64ed..0a00adacbd6a 100644
--- a/lld/test/ELF/loongarch-call36.s
+++ b/lld/test/ELF/loongarch-call36.s
@@ -40,10 +40,6 @@
 # GOTPLT-NEXT: 0x01274730 00000000 00000000 00000000 00000000
 # GOTPLT-NEXT: 0x01274740 00452301 00000000
 
-# RUN: not ld.lld %t/a.o --section-start=.text=0x20000 --section-start=.sec.foo=0x2000020000 -o /dev/null 2>&1 | \
-# RUN:   FileCheck -DFILE=%t/a.o --check-prefix=ERROR-RANGE %s
-# ERROR-RANGE: error: [[FILE]]:(.text+0x0): relocation R_LARCH_CALL36 out of range: 137438953472 is not in [-137439084544, 137438822399]; references 'foo'
-
 ## Impossible case in reality becasue all LoongArch instructions are fixed 4-bytes long.
 # RUN: not ld.lld %t/a.o --section-start=.text=0x20000 --section-start=.sec.foo=0x40001 -o /dev/null 2>&1 | \
 # RUN:   FileCheck -DFILE=%t/a.o --check-prefix=ERROR-ALIGN %s
-- 
2.20.1


From be0c0cd979b6f4e2d778ca16d96a3e465a3ac4dc Mon Sep 17 00:00:00 2001
From: Weining Lu <luweining@loongson.cn>
Date: Mon, 25 Dec 2023 22:41:09 +0800
Subject: [PATCH 3/6] Revert "[lld][test][LoongArch] Remove the test for
 R_LARCH_CALL36 range checking"

This reverts commit 0fbc728dba97149e530cfb7f2ada0283c398a7ce.

In 88548df0fc08, both the .sec.foo and .tex sections used the same
section flags, hence sharing one segment, pushing the output file
size too large. This breaks on many buildbots.

Now assign section .sec.foo different flags ("awx") from .text ("ax")
so that both sections get their own segment.

(cherry picked from commit 6452395561eaae59e38f1df84f5413dffdb9169f)
---
 lld/test/ELF/loongarch-call36.s | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/lld/test/ELF/loongarch-call36.s b/lld/test/ELF/loongarch-call36.s
index 0a00adacbd6a..b593fdf1f604 100644
--- a/lld/test/ELF/loongarch-call36.s
+++ b/lld/test/ELF/loongarch-call36.s
@@ -40,6 +40,10 @@
 # GOTPLT-NEXT: 0x01274730 00000000 00000000 00000000 00000000
 # GOTPLT-NEXT: 0x01274740 00452301 00000000
 
+# RUN: not ld.lld %t/a.o --section-start=.text=0x20000 --section-start=.sec.foo=0x2000020000 -o /dev/null 2>&1 | \
+# RUN:   FileCheck -DFILE=%t/a.o --check-prefix=ERROR-RANGE %s
+# ERROR-RANGE: error: [[FILE]]:(.text+0x0): relocation R_LARCH_CALL36 out of range: 137438953472 is not in [-137439084544, 137438822399]; references 'foo'
+
 ## Impossible case in reality becasue all LoongArch instructions are fixed 4-bytes long.
 # RUN: not ld.lld %t/a.o --section-start=.text=0x20000 --section-start=.sec.foo=0x40001 -o /dev/null 2>&1 | \
 # RUN:   FileCheck -DFILE=%t/a.o --check-prefix=ERROR-ALIGN %s
@@ -59,7 +63,7 @@ _start:
   pcaddu18i $t0, 0
   jirl      $zero, $t0, 0
 
-.section .sec.foo,"ax"
+.section .sec.foo,"awx"
 .global foo
 foo:
   ret
-- 
2.20.1


From cf0d9db4664d59e163d53c62ae1de663092ab2d2 Mon Sep 17 00:00:00 2001
From: Weining Lu <luweining@loongson.cn>
Date: Fri, 10 Nov 2023 13:37:55 +0800
Subject: [PATCH 4/6] [lld][ELF] Add a corner testcase for
 elf::getLoongArchPageDelta

If `page(dest) - page(pc)` is 0xfffffffffff000, i.e. page(pc) is next
to page(dest), and lo12(dest) > 0x7ff, correct %pc64_lo12 and %pc64_hi12
should be both -1 (which can be checked with binutils) but they are both
0 on lld. This patch adds such a test showing lld's incorrect behaviour
and following patch will fix this issue.

(cherry picked from commit e752b58e0d26fc08bca6b2a4e56b05af7f8d8d66)
---
 lld/test/ELF/loongarch-pc-aligned.s | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/lld/test/ELF/loongarch-pc-aligned.s b/lld/test/ELF/loongarch-pc-aligned.s
index 9df3492d1877..f6ac56e5261d 100644
--- a/lld/test/ELF/loongarch-pc-aligned.s
+++ b/lld/test/ELF/loongarch-pc-aligned.s
@@ -260,6 +260,19 @@
 # EXTREME15-NEXT: lu32i.d   $t0, -349526
 # EXTREME15-NEXT: lu52i.d   $t0, $t0, -1093
 
+## FIXME: Correct %pc64_lo20 should be 0xfffff (-1) and %pc64_hi12 should be 0xfff (-1), but current values are:
+## page delta = 0x0000000000000000, page offset = 0x888
+## %pc_lo12   = 0x888 = -1912
+## %pc_hi20   = 0x00000 = 0
+## %pc64_lo20 = 0x00000 = 0
+## %pc64_hi12 = 0x00000 = 0
+# RUN: ld.lld %t/extreme.o --section-start=.rodata=0x0000000012344888 --section-start=.text=0x0000000012345678 -o %t/extreme16
+# RUN: llvm-objdump -d --no-show-raw-insn %t/extreme16 | FileCheck %s --check-prefix=EXTREME16
+# EXTREME16:      addi.d $t0, $zero, -1912
+# EXTREME16-NEXT: pcalau12i $t1, 0
+# EXTREME16-NEXT: lu32i.d   $t0, 0
+# EXTREME16-NEXT: lu52i.d   $t0, $t0, 0
+
 #--- a.s
 .rodata
 x:
-- 
2.20.1


From 11d61b028f306d5ace2b09154781575e88b118cb Mon Sep 17 00:00:00 2001
From: Weining Lu <luweining@loongson.cn>
Date: Sat, 25 Nov 2023 15:44:05 +0800
Subject: [PATCH 5/6] [lld][LoongArch] Add a another corner testcase for
 elf::getLoongArchPageDelta

Similar to e752b58e0d26.

(cherry picked from commit 84a20989c6f72d0f7d04c9981d51c7838e95855c)
---
 lld/ELF/Arch/LoongArch.cpp          |  1 -
 lld/test/ELF/loongarch-pc-aligned.s | 13 +++++++++++++
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/lld/ELF/Arch/LoongArch.cpp b/lld/ELF/Arch/LoongArch.cpp
index 72d9c6838e31..516d02bb9e3f 100644
--- a/lld/ELF/Arch/LoongArch.cpp
+++ b/lld/ELF/Arch/LoongArch.cpp
@@ -168,7 +168,6 @@ uint64_t elf::getLoongArchPageDelta(uint64_t dest, uint64_t pc) {
     result -= 0x10000'0000;
   else if (!negativeA && negativeB)
     result += 0x10000'0000;
-
   return result;
 }
 
diff --git a/lld/test/ELF/loongarch-pc-aligned.s b/lld/test/ELF/loongarch-pc-aligned.s
index f6ac56e5261d..e7950400a5c8 100644
--- a/lld/test/ELF/loongarch-pc-aligned.s
+++ b/lld/test/ELF/loongarch-pc-aligned.s
@@ -273,6 +273,19 @@
 # EXTREME16-NEXT: lu32i.d   $t0, 0
 # EXTREME16-NEXT: lu52i.d   $t0, $t0, 0
 
+## FIXME: Correct %pc64_lo20 should be 0x00000 (0) and %pc64_hi12 should be 0x000 (0), but current values are:
+## page delta = 0xffffffff80000000, page offset = 0x888
+## %pc_lo12   = 0x888 = -1912
+## %pc_hi20   = 0x80000 = -524288
+## %pc64_lo20 = 0xfffff = -1
+## %pc64_hi12 = 0xfff = -1
+# RUN: ld.lld %t/extreme.o --section-start=.rodata=0x000071238ffff888 --section-start=.text=0x0000712310000678 -o %t/extreme17
+# RUN: llvm-objdump -d --no-show-raw-insn %t/extreme17 | FileCheck %s --check-prefix=EXTREME17
+# EXTREME17:      addi.d $t0, $zero, -1912
+# EXTREME17-NEXT: pcalau12i $t1, -524288
+# EXTREME17-NEXT: lu32i.d   $t0, -1
+# EXTREME17-NEXT: lu52i.d   $t0, $t0, -1
+
 #--- a.s
 .rodata
 x:
-- 
2.20.1


From 1ea127e041629ae2df9b9cf6a85e25b6276d4a99 Mon Sep 17 00:00:00 2001
From: Lu Weining <luweining@loongson.cn>
Date: Wed, 10 Jan 2024 18:03:52 +0800
Subject: [PATCH 6/6] [lld][LoongArch] Handle extreme code model relocs
 according to psABI v2.30 (#73387)

psABI v2.30 requires the extreme code model instructions sequence
(pcalau12i+addi.d+lu32i.d+lu52i.d) to be adjacent.

See https://github.com/llvm/llvm-project/pull/71907 and
https://github.com/loongson-community/discussions/issues/17 for details.

(cherry picked from commit 38394a3d0b8b9a1fdc444bdebeba17a19250997d)
---
 lld/ELF/Arch/LoongArch.cpp          | 110 +++++++---------------------
 lld/ELF/InputSection.cpp            |  10 +--
 lld/ELF/Target.h                    |   2 +-
 lld/test/ELF/loongarch-pc-aligned.s | 109 ++++++++++++++-------------
 4 files changed, 93 insertions(+), 138 deletions(-)

diff --git a/lld/ELF/Arch/LoongArch.cpp b/lld/ELF/Arch/LoongArch.cpp
index 516d02bb9e3f..19147a0f6df6 100644
--- a/lld/ELF/Arch/LoongArch.cpp
+++ b/lld/ELF/Arch/LoongArch.cpp
@@ -85,89 +85,33 @@ static uint64_t getLoongArchPage(uint64_t p) {
 static uint32_t lo12(uint32_t val) { return val & 0xfff; }
 
 // Calculate the adjusted page delta between dest and PC.
-uint64_t elf::getLoongArchPageDelta(uint64_t dest, uint64_t pc) {
-  // Consider the large code model access pattern, of which the smaller code
-  // models' access patterns are a subset:
-  //
-  //     pcalau12i       U, %foo_hi20(sym)        ; b in [-0x80000, 0x7ffff]
-  //     addi.d          T, zero, %foo_lo12(sym)  ; a in [-0x800, 0x7ff]
-  //     lu32i.d         T, %foo64_lo20(sym)      ; c in [-0x80000, 0x7ffff]
-  //     lu52i.d         T, T, %foo64_hi12(sym)   ; d in [-0x800, 0x7ff]
-  //     {ldx,stx,add}.* dest, U, T
-  //
-  // Let page(pc) = 0xRRR'QQQQQ'PPPPP'000 and dest = 0xZZZ'YYYYY'XXXXX'AAA,
-  // with RQ, P, ZY, X and A representing the respective bitfields as unsigned
-  // integers. We have:
-  //
-  //     page(dest) = 0xZZZ'YYYYY'XXXXX'000
-  //     - page(pc) = 0xRRR'QQQQQ'PPPPP'000
-  //     ----------------------------------
-  //                  0xddd'ccccc'bbbbb'000
-  //
-  // Now consider the above pattern's actual effects:
-  //
-  //     page(pc)                     0xRRR'QQQQQ'PPPPP'000
-  //     pcalau12i                  + 0xiii'iiiii'bbbbb'000
-  //     addi                       + 0xjjj'jjjjj'kkkkk'AAA
-  //     lu32i.d & lu52i.d          + 0xddd'ccccc'00000'000
-  //     --------------------------------------------------
-  //     dest = U + T
-  //          = ((RQ<<32) + (P<<12) + i + (b<<12)) + (j + k + A + (cd<<32))
-  //          = (((RQ+cd)<<32) + i + j) + (((P+b)<<12) + k) + A
-  //          = (ZY<<32)                + (X<<12)           + A
-  //
-  //     ZY<<32 = (RQ<<32)+(cd<<32)+i+j, X<<12 = (P<<12)+(b<<12)+k
-  //     cd<<32 = (ZY<<32)-(RQ<<32)-i-j, b<<12 = (X<<12)-(P<<12)-k
-  //
-  // where i and k are terms representing the effect of b's and A's sign
-  // extension respectively.
-  //
-  //     i = signed b < 0 ? -0x10000'0000 : 0
-  //     k = signed A < 0 ? -0x1000 : 0
-  //
-  // The j term is a bit complex: it represents the higher half of
-  // sign-extended bits from A that are effectively lost if i == 0 but k != 0,
-  // due to overwriting by lu32i.d & lu52i.d.
-  //
-  //     j = signed A < 0 && signed b >= 0 ? 0x10000'0000 : 0
-  //
-  // The actual effect of the instruction sequence before the final addition,
-  // i.e. our desired result value, is thus:
-  //
-  //     result = (cd<<32) + (b<<12)
-  //            = (ZY<<32)-(RQ<<32)-i-j + (X<<12)-(P<<12)-k
-  //            = ((ZY<<32)+(X<<12)) - ((RQ<<32)+(P<<12)) - i - j - k
-  //            = page(dest) - page(pc) - i - j - k
-  //
-  // when signed A >= 0 && signed b >= 0:
-  //
-  //     i = j = k = 0
-  //     result = page(dest) - page(pc)
-  //
-  // when signed A >= 0 && signed b < 0:
-  //
-  //     i = -0x10000'0000, j = k = 0
-  //     result = page(dest) - page(pc) + 0x10000'0000
-  //
-  // when signed A < 0 && signed b >= 0:
-  //
-  //     i = 0, j = 0x10000'0000, k = -0x1000
-  //     result = page(dest) - page(pc) - 0x10000'0000 + 0x1000
-  //
-  // when signed A < 0 && signed b < 0:
-  //
-  //     i = -0x10000'0000, j = 0, k = -0x1000
-  //     result = page(dest) - page(pc) + 0x1000
-  uint64_t result = getLoongArchPage(dest) - getLoongArchPage(pc);
-  bool negativeA = lo12(dest) > 0x7ff;
-  bool negativeB = (result & 0x8000'0000) != 0;
-
-  if (negativeA)
-    result += 0x1000;
-  if (negativeA && !negativeB)
-    result -= 0x10000'0000;
-  else if (!negativeA && negativeB)
-    result += 0x10000'0000;
+uint64_t elf::getLoongArchPageDelta(uint64_t dest, uint64_t pc, RelType type) {
+  // Note that if the sequence being relocated is `pcalau12i + addi.d + lu32i.d
+  // + lu52i.d`, they must be adjancent so that we can infer the PC of
+  // `pcalau12i` when calculating the page delta for the other two instructions
+  // (lu32i.d and lu52i.d). Compensate all the sign-extensions is a bit
+  // complicated. Just use psABI recommended algorithm.
+  uint64_t pcalau12i_pc;
+  switch (type) {
+  case R_LARCH_PCALA64_LO20:
+  case R_LARCH_GOT64_PC_LO20:
+  case R_LARCH_TLS_IE64_PC_LO20:
+    pcalau12i_pc = pc - 8;
+    break;
+  case R_LARCH_PCALA64_HI12:
+  case R_LARCH_GOT64_PC_HI12:
+  case R_LARCH_TLS_IE64_PC_HI12:
+    pcalau12i_pc = pc - 12;
+    break;
+  default:
+    pcalau12i_pc = pc;
+    break;
+  }
+  uint64_t result = getLoongArchPage(dest) - getLoongArchPage(pcalau12i_pc);
+  if (dest & 0x800)
+    result += 0x1000 - 0x1'0000'0000;
+  if (result & 0x8000'0000)
+    result += 0x1'0000'0000;
   return result;
 }
 
diff --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp
index b178d82407e3..44444b62251d 100644
--- a/lld/ELF/InputSection.cpp
+++ b/lld/ELF/InputSection.cpp
@@ -712,8 +712,8 @@ uint64_t InputSectionBase::getRelocTargetVA(const InputFile *file, RelType type,
     return sym.getGotVA() + a - p;
   case R_LOONGARCH_GOT_PAGE_PC:
     if (sym.hasFlag(NEEDS_TLSGD))
-      return getLoongArchPageDelta(in.got->getGlobalDynAddr(sym) + a, p);
-    return getLoongArchPageDelta(sym.getGotVA() + a, p);
+      return getLoongArchPageDelta(in.got->getGlobalDynAddr(sym) + a, p, type);
+    return getLoongArchPageDelta(sym.getGotVA() + a, p, type);
   case R_MIPS_GOTREL:
     return sym.getVA(a) - in.mipsGot->getGp(file);
   case R_MIPS_GOT_GP:
@@ -763,7 +763,7 @@ uint64_t InputSectionBase::getRelocTargetVA(const InputFile *file, RelType type,
     return 0;
   }
   case R_LOONGARCH_PAGE_PC:
-    return getLoongArchPageDelta(sym.getVA(a), p);
+    return getLoongArchPageDelta(sym.getVA(a), p, type);
   case R_PC:
   case R_ARM_PCA: {
     uint64_t dest;
@@ -798,7 +798,7 @@ uint64_t InputSectionBase::getRelocTargetVA(const InputFile *file, RelType type,
   case R_PPC64_CALL_PLT:
     return sym.getPltVA() + a - p;
   case R_LOONGARCH_PLT_PAGE_PC:
-    return getLoongArchPageDelta(sym.getPltVA() + a, p);
+    return getLoongArchPageDelta(sym.getPltVA() + a, p, type);
   case R_PLT_GOTPLT:
     return sym.getPltVA() + a - in.gotPlt->getVA();
   case R_PPC32_PLTREL:
@@ -860,7 +860,7 @@ uint64_t InputSectionBase::getRelocTargetVA(const InputFile *file, RelType type,
   case R_TLSGD_PC:
     return in.got->getGlobalDynAddr(sym) + a - p;
   case R_LOONGARCH_TLSGD_PAGE_PC:
-    return getLoongArchPageDelta(in.got->getGlobalDynAddr(sym) + a, p);
+    return getLoongArchPageDelta(in.got->getGlobalDynAddr(sym) + a, p, type);
   case R_TLSLD_GOTPLT:
     return in.got->getVA() + in.got->getTlsIndexOff() + a - in.gotPlt->getVA();
   case R_TLSLD_GOT:
diff --git a/lld/ELF/Target.h b/lld/ELF/Target.h
index bf831afa1793..aeabe47f92a1 100644
--- a/lld/ELF/Target.h
+++ b/lld/ELF/Target.h
@@ -229,7 +229,7 @@ void addPPC64SaveRestore();
 uint64_t getPPC64TocBase();
 uint64_t getAArch64Page(uint64_t expr);
 template <typename ELFT> void writeARMCmseImportLib();
-uint64_t getLoongArchPageDelta(uint64_t dest, uint64_t pc);
+uint64_t getLoongArchPageDelta(uint64_t dest, uint64_t pc, RelType type);
 void riscvFinalizeRelax(int passes);
 void mergeRISCVAttributesSections();
 void addArmInputSectionMappingSymbols();
diff --git a/lld/test/ELF/loongarch-pc-aligned.s b/lld/test/ELF/loongarch-pc-aligned.s
index e7950400a5c8..0405961e5f74 100644
--- a/lld/test/ELF/loongarch-pc-aligned.s
+++ b/lld/test/ELF/loongarch-pc-aligned.s
@@ -75,8 +75,8 @@
 ## %pc64_hi12 = 0x444 = 1092
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0x4443333334567111 --section-start=.text=0x0000000012345678 -o %t/extreme0
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme0 | FileCheck %s --check-prefix=EXTREME0
-# EXTREME0:      addi.d $t0, $zero, 273
-# EXTREME0-NEXT: pcalau12i $t1, 139810
+# EXTREME0:      pcalau12i $t1, 139810
+# EXTREME0-NEXT: addi.d $t0, $zero, 273
 # EXTREME0-NEXT: lu32i.d   $t0, 209715
 # EXTREME0-NEXT: lu52i.d   $t0, $t0, 1092
 
@@ -87,8 +87,8 @@
 ## %pc64_hi12 = 0x444 = 1092
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0x4443333334567888 --section-start=.text=0x0000000012345678 -o %t/extreme1
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme1 | FileCheck %s --check-prefix=EXTREME1
-# EXTREME1:      addi.d $t0, $zero, -1912
-# EXTREME1-NEXT: pcalau12i $t1, 139811
+# EXTREME1:      pcalau12i $t1, 139811
+# EXTREME1-NEXT: addi.d $t0, $zero, -1912
 # EXTREME1-NEXT: lu32i.d   $t0, 209714
 # EXTREME1-NEXT: lu52i.d   $t0, $t0, 1092
 
@@ -99,8 +99,8 @@
 ## %pc64_hi12 = 0x444 = 1092
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0x44433333abcde111 --section-start=.text=0x0000000012345678 -o %t/extreme2
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme2 | FileCheck %s --check-prefix=EXTREME2
-# EXTREME2:      addi.d $t0, $zero, 273
-# EXTREME2-NEXT: pcalau12i $t1, -419431
+# EXTREME2:      pcalau12i $t1, -419431
+# EXTREME2-NEXT: addi.d $t0, $zero, 273
 # EXTREME2-NEXT: lu32i.d   $t0, 209716
 # EXTREME2-NEXT: lu52i.d   $t0, $t0, 1092
 
@@ -111,8 +111,8 @@
 ## %pc64_hi12 = 0x444 = 1092
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0x44433333abcde888 --section-start=.text=0x0000000012345678 -o %t/extreme3
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme3 | FileCheck %s --check-prefix=EXTREME3
-# EXTREME3:      addi.d $t0, $zero, -1912
-# EXTREME3-NEXT: pcalau12i $t1, -419430
+# EXTREME3:      pcalau12i $t1, -419430
+# EXTREME3-NEXT: addi.d $t0, $zero, -1912
 # EXTREME3-NEXT: lu32i.d   $t0, 209715
 # EXTREME3-NEXT: lu52i.d   $t0, $t0, 1092
 
@@ -123,8 +123,8 @@
 ## %pc64_hi12 = 0x444 = 1092
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0x444aaaaa34567111 --section-start=.text=0x0000000012345678 -o %t/extreme4
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme4 | FileCheck %s --check-prefix=EXTREME4
-# EXTREME4:      addi.d $t0, $zero, 273
-# EXTREME4-NEXT: pcalau12i $t1, 139810
+# EXTREME4:      pcalau12i $t1, 139810
+# EXTREME4-NEXT: addi.d $t0, $zero, 273
 # EXTREME4-NEXT: lu32i.d   $t0, -349526
 # EXTREME4-NEXT: lu52i.d   $t0, $t0, 1092
 
@@ -135,8 +135,8 @@
 ## %pc64_hi12 = 0x444 = 1092
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0x444aaaaa34567888 --section-start=.text=0x0000000012345678 -o %t/extreme5
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme5 | FileCheck %s --check-prefix=EXTREME5
-# EXTREME5:      addi.d $t0, $zero, -1912
-# EXTREME5-NEXT: pcalau12i $t1, 139811
+# EXTREME5:      pcalau12i $t1, 139811
+# EXTREME5-NEXT: addi.d $t0, $zero, -1912
 # EXTREME5-NEXT: lu32i.d   $t0, -349527
 # EXTREME5-NEXT: lu52i.d   $t0, $t0, 1092
 
@@ -147,8 +147,8 @@
 ## %pc64_hi12 = 0x444 = 1092
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0x444aaaaaabcde111 --section-start=.text=0x0000000012345678 -o %t/extreme6
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme6 | FileCheck %s --check-prefix=EXTREME6
-# EXTREME6:      addi.d $t0, $zero, 273
-# EXTREME6-NEXT: pcalau12i $t1, -419431
+# EXTREME6:      pcalau12i $t1, -419431
+# EXTREME6-NEXT: addi.d $t0, $zero, 273
 # EXTREME6-NEXT: lu32i.d   $t0, -349525
 # EXTREME6-NEXT: lu52i.d   $t0, $t0, 1092
 
@@ -159,8 +159,8 @@
 ## %pc64_hi12 = 0x444 = 1092
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0x444aaaaaabcde888 --section-start=.text=0x0000000012345678 -o %t/extreme7
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme7 | FileCheck %s --check-prefix=EXTREME7
-# EXTREME7:      addi.d $t0, $zero, -1912
-# EXTREME7-NEXT: pcalau12i $t1, -419430
+# EXTREME7:      pcalau12i $t1, -419430
+# EXTREME7-NEXT: addi.d $t0, $zero, -1912
 # EXTREME7-NEXT: lu32i.d   $t0, -349526
 # EXTREME7-NEXT: lu52i.d   $t0, $t0, 1092
 
@@ -171,8 +171,8 @@
 ## %pc64_hi12 = 0xbbb = -1093
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0xbbb3333334567111 --section-start=.text=0x0000000012345678 -o %t/extreme8
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme8 | FileCheck %s --check-prefix=EXTREME8
-# EXTREME8:      addi.d $t0, $zero, 273
-# EXTREME8-NEXT: pcalau12i $t1, 139810
+# EXTREME8:      pcalau12i $t1, 139810
+# EXTREME8-NEXT: addi.d $t0, $zero, 273
 # EXTREME8-NEXT: lu32i.d   $t0, 209715
 # EXTREME8-NEXT: lu52i.d   $t0, $t0, -1093
 
@@ -183,8 +183,8 @@
 ## %pc64_hi12 = 0xbbb = -1093
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0xbbb3333334567888 --section-start=.text=0x0000000012345678 -o %t/extreme9
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme9 | FileCheck %s --check-prefix=EXTREME9
-# EXTREME9:      addi.d $t0, $zero, -1912
-# EXTREME9-NEXT: pcalau12i $t1, 139811
+# EXTREME9:      pcalau12i $t1, 139811
+# EXTREME9-NEXT: addi.d $t0, $zero, -1912
 # EXTREME9-NEXT: lu32i.d   $t0, 209714
 # EXTREME9-NEXT: lu52i.d   $t0, $t0, -1093
 
@@ -195,8 +195,8 @@
 ## %pc64_hi12 = 0xbbb = -1093
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0xbbb33333abcde111 --section-start=.text=0x0000000012345678 -o %t/extreme10
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme10 | FileCheck %s --check-prefix=EXTREME10
-# EXTREME10:      addi.d $t0, $zero, 273
-# EXTREME10-NEXT: pcalau12i $t1, -419431
+# EXTREME10:      pcalau12i $t1, -419431
+# EXTREME10-NEXT: addi.d $t0, $zero, 273
 # EXTREME10-NEXT: lu32i.d   $t0, 209716
 # EXTREME10-NEXT: lu52i.d   $t0, $t0, -1093
 
@@ -207,8 +207,8 @@
 ## %pc64_hi12 = 0xbbb = -1093
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0xbbb33333abcde888 --section-start=.text=0x0000000012345678 -o %t/extreme11
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme11 | FileCheck %s --check-prefix=EXTREME11
-# EXTREME11:      addi.d $t0, $zero, -1912
-# EXTREME11-NEXT: pcalau12i $t1, -419430
+# EXTREME11:      pcalau12i $t1, -419430
+# EXTREME11-NEXT: addi.d $t0, $zero, -1912
 # EXTREME11-NEXT: lu32i.d   $t0, 209715
 # EXTREME11-NEXT: lu52i.d   $t0, $t0, -1093
 
@@ -219,8 +219,8 @@
 ## %pc64_hi12 = 0xbbb = -1093
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0xbbbaaaaa34567111 --section-start=.text=0x0000000012345678 -o %t/extreme12
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme12 | FileCheck %s --check-prefix=EXTREME12
-# EXTREME12:      addi.d $t0, $zero, 273
-# EXTREME12-NEXT: pcalau12i $t1, 139810
+# EXTREME12:      pcalau12i $t1, 139810
+# EXTREME12-NEXT: addi.d $t0, $zero, 273
 # EXTREME12-NEXT: lu32i.d   $t0, -349526
 # EXTREME12-NEXT: lu52i.d   $t0, $t0, -1093
 
@@ -231,8 +231,8 @@
 ## %pc64_hi12 = 0xbbb = -1093
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0xbbbaaaaa34567888 --section-start=.text=0x0000000012345678 -o %t/extreme13
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme13 | FileCheck %s --check-prefix=EXTREME13
-# EXTREME13:      addi.d $t0, $zero, -1912
-# EXTREME13-NEXT: pcalau12i $t1, 139811
+# EXTREME13:      pcalau12i $t1, 139811
+# EXTREME13-NEXT: addi.d $t0, $zero, -1912
 # EXTREME13-NEXT: lu32i.d   $t0, -349527
 # EXTREME13-NEXT: lu52i.d   $t0, $t0, -1093
 
@@ -243,8 +243,8 @@
 ## %pc64_hi12 = 0xbbb = -1093
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0xbbbaaaaaabcde111 --section-start=.text=0x0000000012345678 -o %t/extreme14
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme14 | FileCheck %s --check-prefix=EXTREME14
-# EXTREME14:      addi.d $t0, $zero, 273
-# EXTREME14-NEXT: pcalau12i $t1, -419431
+# EXTREME14:      pcalau12i $t1, -419431
+# EXTREME14-NEXT: addi.d $t0, $zero, 273
 # EXTREME14-NEXT: lu32i.d   $t0, -349525
 # EXTREME14-NEXT: lu52i.d   $t0, $t0, -1093
 
@@ -255,36 +255,47 @@
 ## %pc64_hi12 = 0xbbb = -1093
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0xbbbaaaaaabcde888 --section-start=.text=0x0000000012345678 -o %t/extreme15
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme15 | FileCheck %s --check-prefix=EXTREME15
-# EXTREME15:      addi.d $t0, $zero, -1912
-# EXTREME15-NEXT: pcalau12i $t1, -419430
+# EXTREME15:      pcalau12i $t1, -419430
+# EXTREME15-NEXT: addi.d $t0, $zero, -1912
 # EXTREME15-NEXT: lu32i.d   $t0, -349526
 # EXTREME15-NEXT: lu52i.d   $t0, $t0, -1093
 
-## FIXME: Correct %pc64_lo20 should be 0xfffff (-1) and %pc64_hi12 should be 0xfff (-1), but current values are:
-## page delta = 0x0000000000000000, page offset = 0x888
+## page delta = 0xffffffff00000000, page offset = 0x888
 ## %pc_lo12   = 0x888 = -1912
 ## %pc_hi20   = 0x00000 = 0
-## %pc64_lo20 = 0x00000 = 0
-## %pc64_hi12 = 0x00000 = 0
+## %pc64_lo20 = 0xfffff = -1
+## %pc64_hi12 = 0xfff = -1
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0x0000000012344888 --section-start=.text=0x0000000012345678 -o %t/extreme16
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme16 | FileCheck %s --check-prefix=EXTREME16
-# EXTREME16:      addi.d $t0, $zero, -1912
-# EXTREME16-NEXT: pcalau12i $t1, 0
-# EXTREME16-NEXT: lu32i.d   $t0, 0
-# EXTREME16-NEXT: lu52i.d   $t0, $t0, 0
+# EXTREME16:      pcalau12i $t1, 0
+# EXTREME16-NEXT: addi.d $t0, $zero, -1912
+# EXTREME16-NEXT: lu32i.d   $t0, -1
+# EXTREME16-NEXT: lu52i.d   $t0, $t0, -1
 
-## FIXME: Correct %pc64_lo20 should be 0x00000 (0) and %pc64_hi12 should be 0x000 (0), but current values are:
-## page delta = 0xffffffff80000000, page offset = 0x888
+## page delta = 0x0000000080000000, page offset = 0x888
 ## %pc_lo12   = 0x888 = -1912
 ## %pc_hi20   = 0x80000 = -524288
-## %pc64_lo20 = 0xfffff = -1
-## %pc64_hi12 = 0xfff = -1
+## %pc64_lo20 = 0xfffff = 0
+## %pc64_hi12 = 0xfff = 0
 # RUN: ld.lld %t/extreme.o --section-start=.rodata=0x000071238ffff888 --section-start=.text=0x0000712310000678 -o %t/extreme17
 # RUN: llvm-objdump -d --no-show-raw-insn %t/extreme17 | FileCheck %s --check-prefix=EXTREME17
-# EXTREME17:      addi.d $t0, $zero, -1912
-# EXTREME17-NEXT: pcalau12i $t1, -524288
-# EXTREME17-NEXT: lu32i.d   $t0, -1
-# EXTREME17-NEXT: lu52i.d   $t0, $t0, -1
+# EXTREME17:      pcalau12i $t1, -524288
+# EXTREME17-NEXT: addi.d $t0, $zero, -1912
+# EXTREME17-NEXT: lu32i.d   $t0, 0
+# EXTREME17-NEXT: lu52i.d   $t0, $t0, 0
+
+## A case that pcalau12i, lu32i.d and lu52i.d are in different pages.
+## page delta = 0x0000000080000000, page offset = 0x123
+## %pc_lo12   = 0x111 = 273
+## %pc_hi20   = 0x80000 = -524288
+## %pc64_lo20 = 0x00001 = 1
+## %pc64_hi12 = 0x000 = 0
+# RUN: ld.lld %t/extreme.o --section-start=.rodata=0x80000111 --section-start=.text=0xff8 -o %t/extreme18
+# RUN: llvm-objdump -d --no-show-raw-insn %t/extreme18 | FileCheck %s --check-prefix=EXTREME18
+# EXTREME18:      pcalau12i $t1, -524288
+# EXTREME18-NEXT: addi.d $t0, $zero, 273
+# EXTREME18-NEXT: lu32i.d   $t0, 1
+# EXTREME18-NEXT: lu52i.d   $t0, $t0, 0
 
 #--- a.s
 .rodata
@@ -303,7 +314,7 @@ x:
 .text
 .global _start
 _start:
-    addi.d    $t0, $zero, %pc_lo12(x)
     pcalau12i $t1, %pc_hi20(x)
+    addi.d    $t0, $zero, %pc_lo12(x)
     lu32i.d   $t0, %pc64_lo20(x)
     lu52i.d   $t0, $t0, %pc64_hi12(x)
-- 
2.20.1

