[llvm] [X86AsmBackend] Check fixup value overflow (PR #176827)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Mon Jan 19 13:58:08 PST 2026


https://github.com/MaskRay created https://github.com/llvm/llvm-project/pull/176827

GNU Assembler has a generic error checking for overflowed fixup values
```
y.s:5: Error: value of 8000000000000000 too large for field of 4 bytes at 0000000000000004
```

In contrast, we have had an assertion that may fail for a long time. https://reviews.llvm.org/D70652 improved the status by adding an overflow check for PC-relative fixups, but missed other cases (#116899).

This patch improves the fixup value overflow check:

* Signed (PC-relative): value must be sign-extended, i.e., high bits are all 0s or all 1s.
* Unsigned: both positive and negative values are allowed as long as abs(value) fits (e.g., 1-byte field allows [-255,255]).

The new behavior seems to better resemble GAS.

Note: Currently only PC-relative fixups are treated as signed. GAS treats more fixups (e.g. R_X86_64_32S used in `mov`) as signed.

>From d4a34af31cf5d2875e5d4ffd4723a065459261dc Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Mon, 19 Jan 2026 10:05:37 -0800
Subject: [PATCH] [X86AsmBackend] Check fixup value overflow

GNU Assembler has a generic error checking for overflowed fixup values
```
y.s:5: Error: value of 8000000000000000 too large for field of 4 bytes at 0000000000000004
```

In contrast, we have had an assertion that may fail for a long time.
https://reviews.llvm.org/D70652 improved the status by adding an
overflow check for PC-relative fixups, but missed other cases (#116899).

This patch improves the fixup value overflow check:

* Signed (PC-relative): value must be sign-extended, i.e., high bits are
  all 0s or all 1s.
* Unsigned: both positive and negative values are allowed as long as
  abs(value) fits (e.g., 1-byte field allows [-255,255]).

The new behavior seems to better resemble GAS.

Note: Currently only PC-relative fixups are treated as signed.
GAS treats more fixups (e.g. R_X86_64_32S used in `mov`) as signed.
---
 .../Target/X86/MCTargetDesc/X86AsmBackend.cpp | 27 ++++-----
 llvm/test/MC/X86/Relocations/fixup-overflow.s | 55 +++++++++++++++++++
 .../MC/X86/Relocations/jcxz-loop-overflow.s   | 29 ++++++++++
 llvm/test/MC/X86/x86-jcxz-loop-fixup.s        | 26 ---------
 4 files changed, 98 insertions(+), 39 deletions(-)
 create mode 100644 llvm/test/MC/X86/Relocations/fixup-overflow.s
 create mode 100644 llvm/test/MC/X86/Relocations/jcxz-loop-overflow.s
 delete mode 100644 llvm/test/MC/X86/x86-jcxz-loop-fixup.s

diff --git a/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp b/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp
index 0a98331da4771..b5bcb9202ba25 100644
--- a/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp
+++ b/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp
@@ -698,21 +698,22 @@ void X86AsmBackend::applyFixup(const MCFragment &F, const MCFixup &Fixup,
 
   assert(Fixup.getOffset() + Size <= F.getSize() && "Invalid fixup offset!");
 
-  int64_t SignedValue = static_cast<int64_t>(Value);
-  if (IsResolved && Fixup.isPCRel()) {
-    // check that PC relative fixup fits into the fixup size.
-    if (Size > 0 && !isIntN(Size * 8, SignedValue))
+  // Check fixup value overflow similar to GAS. Fixups emitted as RELA
+  // relocations have a value of 0.
+  //
+  // - Signed: high bits must be all 0s or all 1s (sign extension).
+  // - Unsigned: abs(value) must fit (e.g. 1-byte field allows [-255,255]).
+  //
+  // Currently only PC-relative fixups are treated as signed. GAS treats
+  // more (e.g. FK_Data_4 for R_X86_64_32S) as signed.
+  if (Size && Size < 8) {
+    bool Signed = Fixup.isPCRel();
+    uint64_t Mask = ~uint64_t(0) << (Size * 8 - (Signed ? 1 : 0));
+    if ((Value & Mask) && (Signed ? (Value & Mask) != Mask : (-Value & Mask)))
       getContext().reportError(Fixup.getLoc(),
-                               "value of " + Twine(SignedValue) +
+                               "value of " + Twine(int64_t(Value)) +
                                    " is too large for field of " + Twine(Size) +
-                                   ((Size == 1) ? " byte." : " bytes."));
-  } else {
-    // Check that uppper bits are either all zeros or all ones.
-    // Specifically ignore overflow/underflow as long as the leakage is
-    // limited to the lower bits. This is to remain compatible with
-    // other assemblers.
-    assert((Size == 0 || isIntN(Size * 8 + 1, SignedValue)) &&
-           "Value does not fit in the Fixup field");
+                                   (Size == 1 ? " byte" : " bytes"));
   }
 
   for (unsigned i = 0; i != Size; ++i)
diff --git a/llvm/test/MC/X86/Relocations/fixup-overflow.s b/llvm/test/MC/X86/Relocations/fixup-overflow.s
new file mode 100644
index 0000000000000..96699820e4aab
--- /dev/null
+++ b/llvm/test/MC/X86/Relocations/fixup-overflow.s
@@ -0,0 +1,55 @@
+# RUN: not llvm-mc -filetype=obj -triple=x86_64 %s -o /dev/null 2>&1 | FileCheck %s --implicit-check-not=error:
+
+## Test fixup value overflow diagnostics for non-PC-relative fixups
+## with large immediate values that don't fit in the fixup field.
+
+.intel_syntax noprefix
+# CHECK: :[[#@LINE+1]]:10: error: value of 4294967296 is too large for field of 4 bytes
+mov rcx, s4294967296
+
+## GAS rejects [2147483648, 4294967295] for R_X86_64_32S
+mov rcx, s4294967295
+
+.long s4294967295
+# CHECK: :[[#@LINE+1]]:7: error: value of 4294967296 is too large for field of 4 bytes
+.long s4294967296
+
+.quad s4294967296
+
+# CHECK: :[[#@LINE+1]]:10: error: value of -4294967296 is too large for field of 4 bytes
+mov rcx, sn4294967296
+
+## GAS rejects [-4294967295, -2147483649] for R_X86_64_32S
+mov rcx, sn4294967295
+
+.long sn4294967295
+# CHECK: :[[#@LINE+1]]:7: error: value of -4294967296 is too large for field of 4 bytes
+.long sn4294967296
+
+.set s4294967295, (1<<32)-1
+.set s4294967296, 1<<32
+.set sn4294967295, -(1<<32)+1
+.set sn4294967296, -(1<<32)
+
+.section rip_relative_fixups,"ax", at progbits
+## RIP-relative addressing uses signed 32-bit displacement.
+## Forward: 0x7fffffff fits, 0x80000000 overflows
+mov eax, [rip + .Lend1]
+.space 0x7fffffff
+.Lend1:
+
+# CHECK: :[[#@LINE+1]]:17: error: value of 2147483648 is too large for field of 4 bytes
+mov eax, [rip + .Lend2]
+.space 0x80000000
+.Lend2:
+
+## Backward: -0x80000000 fits, -0x80000001 overflows
+## The mov instruction is 6 bytes, so .space 0x7ffffffa gives displacement -0x80000000.
+.Lstart1:
+.space 0x7ffffffa
+mov eax, [rip + .Lstart1]
+
+# CHECK: :[[#@LINE+3]]:17: error: value of -2147483649 is too large for field of 4 bytes
+.Lstart2:
+.space 0x7ffffffb
+mov eax, [rip + .Lstart2]
diff --git a/llvm/test/MC/X86/Relocations/jcxz-loop-overflow.s b/llvm/test/MC/X86/Relocations/jcxz-loop-overflow.s
new file mode 100644
index 0000000000000..45c1fbcbecfd4
--- /dev/null
+++ b/llvm/test/MC/X86/Relocations/jcxz-loop-overflow.s
@@ -0,0 +1,29 @@
+# RUN: not llvm-mc -filetype=obj -triple=x86_64-linux-gnu %s 2>&1 | FileCheck %s
+
+## Test fixup value overflow diagnostics for PC-relative fixups.
+## jecxz/loop instructions only support 8-bit displacement.
+
+       .balign 128
+label00:
+// CHECK: value of 253 is too large for field of 1 byte
+  jecxz   label01
+// CHECK: value of 251 is too large for field of 1 byte
+  jrcxz   label01
+// CHECK: value of 249 is too large for field of 1 byte
+  loop  label01
+// CHECK: value of 247 is too large for field of 1 byte
+  loope  label01
+// CHECK: value of 245 is too large for field of 1 byte
+  loopne  label01
+        .balign 256
+label01:
+// CHECK: value of -259 is too large for field of 1 byte
+  jecxz   label00
+// CHECK: value of -261 is too large for field of 1 byte
+  jrcxz   label00
+// CHECK: value of -263 is too large for field of 1 byte
+  loop  label00
+// CHECK: value of -265 is too large for field of 1 byte
+  loope  label00
+// CHECK: value of -267 is too large for field of 1 byte
+  loopne  label00
diff --git a/llvm/test/MC/X86/x86-jcxz-loop-fixup.s b/llvm/test/MC/X86/x86-jcxz-loop-fixup.s
deleted file mode 100644
index 219c1bb52eb6b..0000000000000
--- a/llvm/test/MC/X86/x86-jcxz-loop-fixup.s
+++ /dev/null
@@ -1,26 +0,0 @@
-# RUN: not llvm-mc -filetype=obj -triple=x86_64-linux-gnu %s 2>&1 | FileCheck %s
-
-       .balign 128 
-label00:
-// CHECK: value of 253 is too large for field of 1 byte.
-  jecxz   label01
-// CHECK: value of 251 is too large for field of 1 byte.
-  jrcxz   label01
-// CHECK: value of 249 is too large for field of 1 byte.
-  loop  label01
-// CHECK: value of 247 is too large for field of 1 byte. 
-  loope  label01
-// CHECK: value of 245 is too large for field of 1 byte.
-  loopne  label01
-        .balign 256 
-label01:
-// CHECK: value of -259 is too large for field of 1 byte.
-  jecxz   label00
-// CHECK: value of -261 is too large for field of 1 byte.
-  jrcxz   label00
-// CHECK: value of -263 is too large for field of 1 byte.
-  loop  label00
-// CHECK: value of -265 is too large for field of 1 byte.
-  loope  label00
-// CHECK: value of -267 is too large for field of 1 byte.
-  loopne  label00



More information about the llvm-commits mailing list