[llvm] [X86] Restrict offset folding into address mode in 16-bit mode (PR #179399)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Tue Feb 3 08:31:48 PST 2026


https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/179399

>From e16ff3a72fd28fed888dfae955472ad1289c220b Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Mon, 2 Feb 2026 21:49:52 -0800
Subject: [PATCH 1/4] [X86] Don't fold offset into address mode in 16-bit mode

```
extern int X;
unsigned f(void) { return (unsigned)&X - 0xf0000; }
```
compiles (-O1 or above, -fno-pic) to

```
.code16
leal X-983040, %eax  # R_386_16 relocatin, [-65535,65535]
```
in 16-bit mode, which is rejected by both GNU Assembler and integrated
assembler since #176827 .

Conservatively disable offset folding entirely in 16-bit mode to avoid
generating invalid displacements.
---
 llvm/lib/Target/X86/X86ISelDAGToDAG.cpp       |  9 ++++---
 llvm/test/CodeGen/X86/fold-add-16.ll          | 26 +++++++++++++++++++
 .../{dag-large-offset.ll => fold-add-32.ll}   |  0
 3 files changed, 32 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/CodeGen/X86/fold-add-16.ll
 rename llvm/test/CodeGen/X86/{dag-large-offset.ll => fold-add-32.ll} (100%)

diff --git a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
index c81d36a9295c0..2aec805aba497 100644
--- a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
+++ b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
@@ -1852,10 +1852,13 @@ bool X86DAGToDAGISel::foldOffsetIntoAddress(uint64_t Offset,
         !isDispSafeForFrameIndexOrRegBase((uint32_t)Val) &&
         !AM.hasBaseOrIndexReg())
       return true;
-  } else if (AM.hasBaseOrIndexReg() && !isDispSafeForFrameIndexOrRegBase(Val))
-    // For 32-bit X86, make sure the displacement still isn't close to the
-    // expressible limit.
+  } else if (Subtarget->is16Bit() || (AM.hasBaseOrIndexReg() &&
+                                      !isDispSafeForFrameIndexOrRegBase(Val))) {
+    // In 16-bit mode, displacements are limited to [-65535,65535] (
+    // R_386_16 relocation). For simplicity, don't fold offsets. For
+    // 32-bit, ensure the displacement isn't close to the limit.
     return true;
+  }
   AM.Disp = Val;
   return false;
 }
diff --git a/llvm/test/CodeGen/X86/fold-add-16.ll b/llvm/test/CodeGen/X86/fold-add-16.ll
new file mode 100644
index 0000000000000..2b0d35ae01680
--- /dev/null
+++ b/llvm/test/CodeGen/X86/fold-add-16.ll
@@ -0,0 +1,26 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=i386-unknown-code16 < %s | FileCheck %s
+
+;; In 16-bit mode, the displacement must fit in 16 bits.
+;; Test that we don't fold a large offset into the address mode.
+;; The implementation simply doesn't fold any offset.
+
+ at X = external global i32, align 4
+
+define i32 @small_offset() {
+; CHECK-LABEL: small_offset:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $X, %eax
+; CHECK-NEXT:    addl $-4, %eax
+; CHECK-NEXT:    retl
+  ret i32 sub (i32 ptrtoint (ptr @X to i32), i32 4)
+}
+
+define i32 @large_offset() {
+; CHECK-LABEL: large_offset:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $X, %eax
+; CHECK-NEXT:    addl $-65536, %eax # imm = 0xFFFF0000
+; CHECK-NEXT:    retl
+  ret i32 sub (i32 ptrtoint (ptr @X to i32), i32 65536)
+}
diff --git a/llvm/test/CodeGen/X86/dag-large-offset.ll b/llvm/test/CodeGen/X86/fold-add-32.ll
similarity index 100%
rename from llvm/test/CodeGen/X86/dag-large-offset.ll
rename to llvm/test/CodeGen/X86/fold-add-32.ll

>From 016d1d9f40cbb913e68a02748493989c0dd91176 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Mon, 2 Feb 2026 23:06:27 -0800
Subject: [PATCH 2/4] range

---
 llvm/lib/Target/X86/X86ISelDAGToDAG.cpp | 14 ++++----
 llvm/test/CodeGen/X86/fold-add-16.ll    | 43 ++++++++++++++++++-------
 2 files changed, 39 insertions(+), 18 deletions(-)

diff --git a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
index 2aec805aba497..76fba0a9b1e61 100644
--- a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
+++ b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
@@ -1852,13 +1852,15 @@ bool X86DAGToDAGISel::foldOffsetIntoAddress(uint64_t Offset,
         !isDispSafeForFrameIndexOrRegBase((uint32_t)Val) &&
         !AM.hasBaseOrIndexReg())
       return true;
-  } else if (Subtarget->is16Bit() || (AM.hasBaseOrIndexReg() &&
-                                      !isDispSafeForFrameIndexOrRegBase(Val))) {
-    // In 16-bit mode, displacements are limited to [-65535,65535] (
-    // R_386_16 relocation). For simplicity, don't fold offsets. For
-    // 32-bit, ensure the displacement isn't close to the limit.
+  } else if (Subtarget->is16Bit()) {
+    // In 16-bit mode, displacements are limited to [-65535,65535] (R_386_16
+    // relocation).
+    if (Val < -65535 || Val > 65535)
+      return true;
+  } else if (AM.hasBaseOrIndexReg() && !isDispSafeForFrameIndexOrRegBase(Val))
+    // For 32-bit X86, make sure the displacement still isn't close to the
+    // expressible limit.
     return true;
-  }
   AM.Disp = Val;
   return false;
 }
diff --git a/llvm/test/CodeGen/X86/fold-add-16.ll b/llvm/test/CodeGen/X86/fold-add-16.ll
index 2b0d35ae01680..5c038381eccd5 100644
--- a/llvm/test/CodeGen/X86/fold-add-16.ll
+++ b/llvm/test/CodeGen/X86/fold-add-16.ll
@@ -1,26 +1,45 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
 ; RUN: llc -mtriple=i386-unknown-code16 < %s | FileCheck %s
 
-;; In 16-bit mode, the displacement must fit in 16 bits.
-;; Test that we don't fold a large offset into the address mode.
-;; The implementation simply doesn't fold any offset.
+;; In 16-bit mode, displacements are limited to [-65535,65535] (R_386_16).
+;; Test boundary conditions for offset folding.
 
 @X = external global i32, align 4
 
-define i32 @small_offset() {
-; CHECK-LABEL: small_offset:
+;; -65535 is within range, should fold
+define i32 @neg_65535() {
+; CHECK-LABEL: neg_65535:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    movl $X, %eax
-; CHECK-NEXT:    addl $-4, %eax
+; CHECK-NEXT:    leal X+65535, %eax
 ; CHECK-NEXT:    retl
-  ret i32 sub (i32 ptrtoint (ptr @X to i32), i32 4)
+  ret i32 add (i32 ptrtoint (ptr @X to i32), i32 65535)
 }
 
-define i32 @large_offset() {
-; CHECK-LABEL: large_offset:
+;; 65535 is within range, should fold
+define i32 @pos_65535() {
+; CHECK-LABEL: pos_65535:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    movl $X, %eax
-; CHECK-NEXT:    addl $-65536, %eax # imm = 0xFFFF0000
+; CHECK-NEXT:    leal X-65535, %eax
+; CHECK-NEXT:    retl
+  ret i32 sub (i32 ptrtoint (ptr @X to i32), i32 65535)
+}
+
+;; -65536 is outside range, should NOT fold
+define i32 @neg_65536() {
+; CHECK-LABEL: neg_65536:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $65536, %eax # imm = 0x10000
+; CHECK-NEXT:    leal X(%eax), %eax
+; CHECK-NEXT:    retl
+  ret i32 add (i32 ptrtoint (ptr @X to i32), i32 65536)
+}
+
+;; 65536 is outside range, should NOT fold
+define i32 @pos_65536() {
+; CHECK-LABEL: pos_65536:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $-65536, %eax # imm = 0xFFFF0000
+; CHECK-NEXT:    leal X(%eax), %eax
 ; CHECK-NEXT:    retl
   ret i32 sub (i32 ptrtoint (ptr @X to i32), i32 65536)
 }

>From e1420db05685afbdf4e72f561b9cb072a7718855 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Tue, 3 Feb 2026 08:26:49 -0800
Subject: [PATCH 3/4] better comment

---
 llvm/lib/Target/X86/X86ISelDAGToDAG.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
index 76fba0a9b1e61..291640557e4f5 100644
--- a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
+++ b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
@@ -1853,8 +1853,8 @@ bool X86DAGToDAGISel::foldOffsetIntoAddress(uint64_t Offset,
         !AM.hasBaseOrIndexReg())
       return true;
   } else if (Subtarget->is16Bit()) {
-    // In 16-bit mode, displacements are limited to [-65535,65535] (R_386_16
-    // relocation).
+    // In 16-bit mode, displacements are limited to [-65535,65535] for FK_Data_2
+    // fixups of unknown signedness. See X86AsmBackend::applyFixup.
     if (Val < -65535 || Val > 65535)
       return true;
   } else if (AM.hasBaseOrIndexReg() && !isDispSafeForFrameIndexOrRegBase(Val))

>From e0f0169062d449d6ca77725835d93129c949df2a Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Tue, 3 Feb 2026 08:31:35 -0800
Subject: [PATCH 4/4] use UINT16_MAX

---
 llvm/lib/Target/X86/X86ISelDAGToDAG.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
index 291640557e4f5..eb425a10acf09 100644
--- a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
+++ b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
@@ -1855,7 +1855,7 @@ bool X86DAGToDAGISel::foldOffsetIntoAddress(uint64_t Offset,
   } else if (Subtarget->is16Bit()) {
     // In 16-bit mode, displacements are limited to [-65535,65535] for FK_Data_2
     // fixups of unknown signedness. See X86AsmBackend::applyFixup.
-    if (Val < -65535 || Val > 65535)
+    if (Val < -(int64_t)UINT16_MAX || Val > (int64_t)UINT16_MAX)
       return true;
   } else if (AM.hasBaseOrIndexReg() && !isDispSafeForFrameIndexOrRegBase(Val))
     // For 32-bit X86, make sure the displacement still isn't close to the



More information about the llvm-commits mailing list