[MC] [AArch64] Support resolving signed fixups for :abs_g0_s: etc.

Summary: This patch is a follow-up to D55896.

Reviewers: efriedma, mstorsjo

Reviewed By: efriedma

Subscribers: javed.absar, kristof.beyls, llvm-commits

Differential Revision: https://reviews.llvm.org/D56029

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@350606 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/lib/Target/AArch64/MCTargetDesc/AArch64AsmBackend.cpp b/lib/Target/AArch64/MCTargetDesc/AArch64AsmBackend.cpp
index 76be19b..ed89d99 100644
--- a/lib/Target/AArch64/MCTargetDesc/AArch64AsmBackend.cpp
+++ b/lib/Target/AArch64/MCTargetDesc/AArch64AsmBackend.cpp
@@ -218,20 +218,16 @@
   case AArch64::fixup_aarch64_movw: {
     AArch64MCExpr::VariantKind RefKind =
         static_cast<AArch64MCExpr::VariantKind>(Target.getRefKind());
-    if (AArch64MCExpr::getSymbolLoc(RefKind) != AArch64MCExpr::VK_ABS) {
-      if (AArch64MCExpr::getSymbolLoc(RefKind) != AArch64MCExpr::VK_SABS) {
-        // VK_GOTTPREL, VK_TPREL, VK_DTPREL are movw fixups, but they can't
-        // ever be resolved in the assembler.
-        Ctx.reportError(Fixup.getLoc(),
-                        "relocation for a thread-local variable points to an "
-                        "absolute symbol");
-        return Value;
-      }
+    if (AArch64MCExpr::getSymbolLoc(RefKind) != AArch64MCExpr::VK_ABS &&
+        AArch64MCExpr::getSymbolLoc(RefKind) != AArch64MCExpr::VK_SABS) {
+      // VK_GOTTPREL, VK_TPREL, VK_DTPREL are movw fixups, but they can't
+      // ever be resolved in the assembler.
       Ctx.reportError(Fixup.getLoc(),
-                      "resolvable R_AARCH64_MOVW_SABS_G* fixups are not "
-                       "yet implemented");
+                      "relocation for a thread-local variable points to an "
+                      "absolute symbol");
       return Value;
     }
+
     if (!IsResolved) {
       // FIXME: Figure out when this can actually happen, and verify our
       // behavior.
@@ -239,25 +235,57 @@
                                       "implemented");
       return Value;
     }
-    switch (AArch64MCExpr::getAddressFrag(RefKind)) {
-    case AArch64MCExpr::VK_G0:
-      break;
-    case AArch64MCExpr::VK_G1:
-      Value = Value >> 16;
-      break;
-    case AArch64MCExpr::VK_G2:
-      Value = Value >> 32;
-      break;
-    case AArch64MCExpr::VK_G3:
-      Value = Value >> 48;
-      break;
-    default:
-      llvm_unreachable("Variant kind doesn't correspond to fixup");
+
+    if (AArch64MCExpr::getSymbolLoc(RefKind) == AArch64MCExpr::VK_SABS) {
+      switch (AArch64MCExpr::getAddressFrag(RefKind)) {
+      case AArch64MCExpr::VK_G0:
+        break;
+      case AArch64MCExpr::VK_G1:
+        SignedValue = SignedValue >> 16;
+        break;
+      case AArch64MCExpr::VK_G2:
+        SignedValue = SignedValue >> 32;
+        break;
+      case AArch64MCExpr::VK_G3:
+        SignedValue = SignedValue >> 48;
+        break;
+      default:
+        llvm_unreachable("Variant kind doesn't correspond to fixup");
+      }
+
+    } else {
+      switch (AArch64MCExpr::getAddressFrag(RefKind)) {
+      case AArch64MCExpr::VK_G0:
+        break;
+      case AArch64MCExpr::VK_G1:
+        Value = Value >> 16;
+        break;
+      case AArch64MCExpr::VK_G2:
+        Value = Value >> 32;
+        break;
+      case AArch64MCExpr::VK_G3:
+        Value = Value >> 48;
+        break;
+      default:
+        llvm_unreachable("Variant kind doesn't correspond to fixup");
+      }
     }
-    if (RefKind & AArch64MCExpr::VK_NC)
+
+    if (RefKind & AArch64MCExpr::VK_NC) {
       Value &= 0xFFFF;
-    else if (Value > 0xFFFF)
+    }
+    else if (RefKind & AArch64MCExpr::VK_SABS) {
+      if (SignedValue > 0xFFFF || SignedValue < -0xFFFF)
+        Ctx.reportError(Fixup.getLoc(), "fixup value out of range");
+
+      // Invert the negative immediate because it will feed into a MOVN.
+      if (SignedValue < 0)
+        SignedValue = ~SignedValue;
+      Value = static_cast<uint64_t>(SignedValue);
+    }
+    else if (Value > 0xFFFF) {
       Ctx.reportError(Fixup.getLoc(), "fixup value out of range");
+    }
     return Value;
   }
   case AArch64::fixup_aarch64_pcrel_branch14:
@@ -336,6 +364,7 @@
     return; // Doesn't change encoding.
   MCFixupKindInfo Info = getFixupKindInfo(Fixup.getKind());
   MCContext &Ctx = Asm.getContext();
+  int64_t SignedValue = static_cast<int64_t>(Value);
   // Apply any target-specific value adjustments.
   Value = adjustFixupValue(Fixup, Target, Value, Ctx, TheTriple, IsResolved);
 
@@ -364,6 +393,19 @@
       Data[Offset + Idx] |= uint8_t((Value >> (i * 8)) & 0xff);
     }
   }
+
+  // FIXME: getFixupKindInfo() and getFixupKindNumBytes() could be fixed to
+  // handle this more cleanly. This may affect the output of -show-mc-encoding.
+  AArch64MCExpr::VariantKind RefKind =
+    static_cast<AArch64MCExpr::VariantKind>(Target.getRefKind());
+  if (RefKind & AArch64MCExpr::VK_SABS) {
+    // If the immediate is negative, generate MOVN else MOVZ.
+    // (Bit 30 = 0) ==> MOVN, (Bit 30 = 1) ==> MOVZ.
+    if (SignedValue < 0)
+      Data[Offset + 3] &= ~(1 << 6);
+    else
+      Data[Offset + 3] |= (1 << 6);
+  }
 }
 
 bool AArch64AsmBackend::mayNeedRelaxation(const MCInst &Inst,
diff --git a/test/MC/AArch64/fixup-absolute-signed.s b/test/MC/AArch64/fixup-absolute-signed.s
new file mode 100644
index 0000000..3dd93c9
--- /dev/null
+++ b/test/MC/AArch64/fixup-absolute-signed.s
@@ -0,0 +1,44 @@
+// RUN: llvm-mc -triple aarch64--none-eabi -filetype obj < %s -o - | llvm-objdump -d - | FileCheck %s
+
+onepart_before = 12345
+twopart_before = -12345678
+threepart_before = -1234567890
+
+// CHECK: movn     x0, #0, lsl #32
+// CHECK: movn     x0, #0, lsl #32
+movz x0, #:abs_g2_s:threepart_before
+movz x0, #:abs_g2_s:threepart_after
+
+// CHECK: movk    x0, #65535, lsl #32
+// CHECK: movk    x0, #65535, lsl #32
+movk x0, #:abs_g2_nc:threepart_before
+movk x0, #:abs_g2_nc:threepart_after
+
+// CHECK: mov     x0, #-12320769
+// CHECK: mov     x0, #-12320769
+movz x0, #:abs_g1_s:twopart_before
+movz x0, #:abs_g1_s:twopart_after
+
+// CHECK: movk    x0, #46697, lsl #16
+// CHECK: movk    x0, #46697, lsl #16
+movk x0, #:abs_g1_nc:threepart_before
+movk x0, #:abs_g1_nc:threepart_after
+
+// CHECK: mov     x0, #12345
+// CHECK: mov     x0, #12345
+movz x0, #:abs_g0_s:onepart_before
+movz x0, #:abs_g0_s:onepart_after
+
+// CHECK: movk    x0, #64814
+// CHECK: movk    x0, #64814
+movk x0, #:abs_g0_nc:threepart_before
+movk x0, #:abs_g0_nc:threepart_after
+
+// CHECK: mov     x0, #12345
+// CHECK: mov     x0, #12345
+movn x0, #:abs_g0_s:onepart_before
+movn x0, #:abs_g0_s:onepart_after
+
+onepart_after = 12345
+twopart_after = -12345678
+threepart_after = -1234567890
diff --git a/test/MC/AArch64/fixup-out-of-range.s b/test/MC/AArch64/fixup-out-of-range.s
index 749031e..500c1d1 100644
--- a/test/MC/AArch64/fixup-out-of-range.s
+++ b/test/MC/AArch64/fixup-out-of-range.s
@@ -61,9 +61,12 @@
 // CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: fixup value out of range
   movz x0, #:abs_g1:value2
 
-// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: resolvable R_AARCH64_MOVW_SABS_G* fixups are not yet implemented
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: fixup value out of range
   movz x0, #:abs_g0_s:value1
 
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: fixup value out of range
+  movz x0, #:abs_g1_s:value2
+
 // CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: relocation for a thread-local variable points to an absolute symbol
   movz x0, #:tprel_g0:value1