[ELF][MIPS] Make R_MIPS_LO16 a relative relocation if it references _gp_disp symbol

The _gp_disp symbol designates offset between start of function and 'gp'
pointer into GOT. The following code is a typical MIPS function preamble
used to setup $gp register:

lui    $gp, %hi(_gp_disp)
addi   $gp, $gp, %lo(_gp_disp)

To calculate R_MIPS_HI16 / R_MIPS_LO16 relocations results we use
the following formulas:

%hi(_gp - P + A)
%lo(_gp - P + A + 4),
where _gp is a value of _gp symbol, A is addend, and P current address.

The R_MIPS_LO16 relocation references _gp_disp symbol is always the second
instruction. That is why we need four byte adjustments. The patch assigns
R_PC type for R_MIPS_LO16 relocation and adjusts its addend by 4. That fix
R_MIPS_LO16 calculation.

For details see p. 4-19 at ftp://www.linux-mips.org/pub/linux/mips/doc/ABI/mipsabi.pdf

Differential Revision: http://reviews.llvm.org/D19115

llvm-svn: 266368
diff --git a/lld/ELF/Target.cpp b/lld/ELF/Target.cpp
index 8b12d75..240eb98 100644
--- a/lld/ELF/Target.cpp
+++ b/lld/ELF/Target.cpp
@@ -1579,6 +1579,7 @@
   default:
     return R_ABS;
   case R_MIPS_HI16:
+  case R_MIPS_LO16:
     // MIPS _gp_disp designates offset between start of function and 'gp'
     // pointer into GOT. __gnu_local_gp is equal to the current value of
     // the 'gp'. Therefore any relocations against them do not require
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index 31f2d3e..7a75d23 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -462,14 +462,22 @@
       if (auto *S = dyn_cast<SharedSymbol<ELFT>>(&Body))
         S->File->IsUsed = true;
 
+    RelExpr Expr = Target->getRelExpr(Type, Body);
     uintX_t Addend = getAddend<ELFT>(RI);
     const uint8_t *BufLoc = Buf + RI.r_offset;
     if (!RelTy::IsRela)
       Addend += Target->getImplicitAddend(BufLoc, Type);
-    if (Config->EMachine == EM_MIPS)
+    if (Config->EMachine == EM_MIPS) {
       Addend += findMipsPairedAddend<ELFT>(Buf, BufLoc, Body, &RI, E);
+      if (Type == R_MIPS_LO16 && Expr == R_PC)
+        // R_MIPS_LO16 expression has R_PC type iif the target is _gp_disp
+        // symbol. In that case we should use the following formula for
+        // calculation "AHL + GP – P + 4". Let's add 4 right here.
+        // For details see p. 4-19 at
+        // ftp://www.linux-mips.org/pub/linux/mips/doc/ABI/mipsabi.pdf
+        Addend += 4;
+    }
 
-    RelExpr Expr = Target->getRelExpr(Type, Body);
     if (unsigned Processed =
             handleTlsRelocation<ELFT>(Type, Body, C, Offset, Addend, Expr)) {
       I += (Processed - 1);