Skip to content

[X86] Use unsigned comparison for stack clash probing loop#192355

Merged
nickdesaulniers merged 1 commit into
llvm:mainfrom
nickdesaulniers:stack_clash_signed_comp
Apr 16, 2026
Merged

[X86] Use unsigned comparison for stack clash probing loop#192355
nickdesaulniers merged 1 commit into
llvm:mainfrom
nickdesaulniers:stack_clash_signed_comp

Conversation

@nickdesaulniers

Copy link
Copy Markdown
Member

The stack clash probing loop generated in EmitLoweredProbedAlloca used
a signed comparison (X86::COND_GE) to determine when the allocation
target had been reached.

In 32-bit mode, memory addresses above 0x80000000 have the sign bit
set. If the stack pointer lands in this region, treating the addresses
as signed integers causes the comparison logic to fail. This leads to
incorrect loop execution, resulting in an infinite loop and a crash
(segmentation fault) when setting up custom stacks for pthreads mapped
above 0x80000000 in a 32b process.

This patch changes the condition code to X86::COND_AE (Above or
Equal), which generates an unsigned comparison. This ensures that
addresses are treated correctly as unsigned quantities on all targets.

On 64-bit systems, this change has no practical effect on valid
user-space addresses because they do not use the sign bit (being
restricted to the lower half of the address space). However, using
unsigned comparison is the correct behavior for pointer arithmetic and
bounds checks.

Reported-by: Wonsik Kim wonsik@google.com

The stack clash probing loop generated in `EmitLoweredProbedAlloca` used
a signed comparison (`X86::COND_GE`) to determine when the allocation
target had been reached.

In 32-bit mode, memory addresses above `0x80000000` have the sign bit
set. If the stack pointer lands in this region, treating the addresses
as signed integers causes the comparison logic to fail. This leads to
incorrect loop execution, resulting in an infinite loop and a crash
(segmentation fault) when setting up custom stacks for pthreads mapped
above `0x80000000` in a 32b process.

This patch changes the condition code to `X86::COND_AE` (Above or
Equal), which generates an unsigned comparison. This ensures that
addresses are treated correctly as unsigned quantities on all targets.

On 64-bit systems, this change has no practical effect on valid
user-space addresses because they do not use the sign bit (being
restricted to the lower half of the address space). However, using
unsigned comparison is the correct behavior for pointer arithmetic and
bounds checks.

Reported-by: Wonsik Kim <wonsik@google.com>
@llvmbot

llvmbot commented Apr 15, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-backend-x86

Author: Nick Desaulniers (nickdesaulniers)

Changes

The stack clash probing loop generated in EmitLoweredProbedAlloca used
a signed comparison (X86::COND_GE) to determine when the allocation
target had been reached.

In 32-bit mode, memory addresses above 0x80000000 have the sign bit
set. If the stack pointer lands in this region, treating the addresses
as signed integers causes the comparison logic to fail. This leads to
incorrect loop execution, resulting in an infinite loop and a crash
(segmentation fault) when setting up custom stacks for pthreads mapped
above 0x80000000 in a 32b process.

This patch changes the condition code to X86::COND_AE (Above or
Equal), which generates an unsigned comparison. This ensures that
addresses are treated correctly as unsigned quantities on all targets.

On 64-bit systems, this change has no practical effect on valid
user-space addresses because they do not use the sign bit (being
restricted to the lower half of the address space). However, using
unsigned comparison is the correct behavior for pointer arithmetic and
bounds checks.

Reported-by: Wonsik Kim <wonsik@google.com>


Full diff: https://github.com/llvm/llvm-project/pull/192355.diff

3 Files Affected:

  • (modified) llvm/lib/Target/X86/X86ISelLowering.cpp (+1-1)
  • (modified) llvm/test/CodeGen/X86/stack-clash-dynamic-alloca.ll (+4-4)
  • (modified) llvm/test/CodeGen/X86/stack-clash-small-alloc-medium-align.ll (+2-2)
diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index e647286301d6b..6a00f57e78dd3 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -37069,7 +37069,7 @@ X86TargetLowering::EmitLoweredProbedAlloca(MachineInstr &MI,
 
   BuildMI(testMBB, MIMD, TII->get(X86::JCC_1))
       .addMBB(tailMBB)
-      .addImm(X86::COND_GE);
+      .addImm(X86::COND_AE);
   testMBB->addSuccessor(blockMBB);
   testMBB->addSuccessor(tailMBB);
 
diff --git a/llvm/test/CodeGen/X86/stack-clash-dynamic-alloca.ll b/llvm/test/CodeGen/X86/stack-clash-dynamic-alloca.ll
index b7d1b593d1ab6..b89532e0f17a1 100644
--- a/llvm/test/CodeGen/X86/stack-clash-dynamic-alloca.ll
+++ b/llvm/test/CodeGen/X86/stack-clash-dynamic-alloca.ll
@@ -16,12 +16,12 @@ define i32 @foo(i32 %n) local_unnamed_addr #0 {
 ; CHECK-X86-64-NEXT:    andq $-16, %rcx
 ; CHECK-X86-64-NEXT:    subq %rcx, %rax
 ; CHECK-X86-64-NEXT:    cmpq %rsp, %rax
-; CHECK-X86-64-NEXT:    jge .LBB0_3
+; CHECK-X86-64-NEXT:    jae .LBB0_3
 ; CHECK-X86-64-NEXT:  .LBB0_2: # =>This Inner Loop Header: Depth=1
 ; CHECK-X86-64-NEXT:    xorq $0, (%rsp)
 ; CHECK-X86-64-NEXT:    subq $4096, %rsp # imm = 0x1000
 ; CHECK-X86-64-NEXT:    cmpq %rsp, %rax
-; CHECK-X86-64-NEXT:    jl .LBB0_2
+; CHECK-X86-64-NEXT:    jb .LBB0_2
 ; CHECK-X86-64-NEXT:  .LBB0_3:
 ; CHECK-X86-64-NEXT:    movq %rax, %rsp
 ; CHECK-X86-64-NEXT:    movl $1, 4792(%rax)
@@ -45,12 +45,12 @@ define i32 @foo(i32 %n) local_unnamed_addr #0 {
 ; CHECK-X86-32-NEXT:    andl $-16, %ecx
 ; CHECK-X86-32-NEXT:    subl %ecx, %eax
 ; CHECK-X86-32-NEXT:    cmpl %esp, %eax
-; CHECK-X86-32-NEXT:    jge .LBB0_3
+; CHECK-X86-32-NEXT:    jae .LBB0_3
 ; CHECK-X86-32-NEXT:  .LBB0_2: # =>This Inner Loop Header: Depth=1
 ; CHECK-X86-32-NEXT:    xorl $0, (%esp)
 ; CHECK-X86-32-NEXT:    subl $4096, %esp # imm = 0x1000
 ; CHECK-X86-32-NEXT:    cmpl %esp, %eax
-; CHECK-X86-32-NEXT:    jl .LBB0_2
+; CHECK-X86-32-NEXT:    jb .LBB0_2
 ; CHECK-X86-32-NEXT:  .LBB0_3:
 ; CHECK-X86-32-NEXT:    movl %eax, %esp
 ; CHECK-X86-32-NEXT:    movl $1, 4792(%eax)
diff --git a/llvm/test/CodeGen/X86/stack-clash-small-alloc-medium-align.ll b/llvm/test/CodeGen/X86/stack-clash-small-alloc-medium-align.ll
index ccf7e1d56da90..01a1cb136a49a 100644
--- a/llvm/test/CodeGen/X86/stack-clash-small-alloc-medium-align.ll
+++ b/llvm/test/CodeGen/X86/stack-clash-small-alloc-medium-align.ll
@@ -103,12 +103,12 @@ define i32 @foo4(i64 %i) local_unnamed_addr #0 {
 ; CHECK-NEXT:    andq $-16, %rcx
 ; CHECK-NEXT:    subq %rcx, %rax
 ; CHECK-NEXT:    cmpq %rsp, %rax
-; CHECK-NEXT:    jge .LBB3_3
+; CHECK-NEXT:    jae .LBB3_3
 ; CHECK-NEXT:  .LBB3_2: # =>This Inner Loop Header: Depth=1
 ; CHECK-NEXT:    xorq $0, (%rsp)
 ; CHECK-NEXT:    subq $4096, %rsp # imm = 0x1000
 ; CHECK-NEXT:    cmpq %rsp, %rax
-; CHECK-NEXT:    jl .LBB3_2
+; CHECK-NEXT:    jb .LBB3_2
 ; CHECK-NEXT:  .LBB3_3:
 ; CHECK-NEXT:    andq $-64, %rax
 ; CHECK-NEXT:    movq %rax, %rsp

@Sharjeel-Khan Sharjeel-Khan requested a review from RKSimon April 15, 2026 22:47

@Sharjeel-Khan Sharjeel-Khan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but I added RKSimon since he works on X86 lowering.

@serge-sans-paille serge-sans-paille left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@serge-sans-paille

Copy link
Copy Markdown
Contributor

Did you check if we have the same issue on other non-intel architectures?

@nickdesaulniers

nickdesaulniers commented Apr 16, 2026

Copy link
Copy Markdown
Member Author

Did you check if we have the same issue on other non-intel architectures?

It looks like only X86TargetLowering has the method EmitLoweredProbedAlloca.

I took a quick peek at:

  • llvm-project/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
  • llvm-project/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
  • llvm-project/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
  • llvm-project/llvm/lib/Target/PowerPC/PPCISelLowering.cpp
  • llvm-project/llvm/lib/Target/PowerPC/PPCRegisterInfo.cpp

which reference *PROBED_ALLOCA*.

It looks like RISCV 32b has the same issue; it's using BLT instead of BLTU. I'll post a second patch for that. The rest look fine FWICT.

@nickdesaulniers nickdesaulniers merged commit 7aa2b04 into llvm:main Apr 16, 2026
12 checks passed
@nickdesaulniers nickdesaulniers deleted the stack_clash_signed_comp branch April 16, 2026 16:15
nickdesaulniers added a commit that referenced this pull request Apr 16, 2026
The stack clash probing loop generated in `emitDynamicProbedAlloc` used
a signed comparison (`RISCV::COND_BLT`) to determine when the allocation
target had been reached.

In 32-bit mode, memory addresses above `0x80000000` have the sign bit
set. If the stack pointer lands in this region, treating the addresses
as signed integers causes the comparison logic to fail.

This patch changes the condition code to `RISCV::COND_BLTU` (Branch if
Less Than Unsigned), which generates an unsigned comparison. This
ensures that addresses are treated correctly as unsigned quantities on
all targets.

On 64-bit systems, this change has no practical effect on valid
user-space addresses because they do not use the sign bit (being
restricted to the lower half of the address space). However, using
unsigned comparison is the correct behavior for pointer arithmetic and
bounds checks.

Link: #192355
alexfh pushed a commit to alexfh/llvm-project that referenced this pull request Apr 18, 2026
The stack clash probing loop generated in `EmitLoweredProbedAlloca` used
a signed comparison (`X86::COND_GE`) to determine when the allocation
target had been reached.

In 32-bit mode, memory addresses above `0x80000000` have the sign bit
set. If the stack pointer lands in this region, treating the addresses
as signed integers causes the comparison logic to fail. This leads to
incorrect loop execution, resulting in an infinite loop and a crash
(segmentation fault) when setting up custom stacks for pthreads mapped
above `0x80000000` in a 32b process.

This patch changes the condition code to `X86::COND_AE` (Above or
Equal), which generates an unsigned comparison. This ensures that
addresses are treated correctly as unsigned quantities on all targets.

On 64-bit systems, this change has no practical effect on valid
user-space addresses because they do not use the sign bit (being
restricted to the lower half of the address space). However, using
unsigned comparison is the correct behavior for pointer arithmetic and
bounds checks.

Reported-by: Wonsik Kim <wonsik@google.com>
alexfh pushed a commit to alexfh/llvm-project that referenced this pull request Apr 18, 2026
…2485)

The stack clash probing loop generated in `emitDynamicProbedAlloc` used
a signed comparison (`RISCV::COND_BLT`) to determine when the allocation
target had been reached.

In 32-bit mode, memory addresses above `0x80000000` have the sign bit
set. If the stack pointer lands in this region, treating the addresses
as signed integers causes the comparison logic to fail.

This patch changes the condition code to `RISCV::COND_BLTU` (Branch if
Less Than Unsigned), which generates an unsigned comparison. This
ensures that addresses are treated correctly as unsigned quantities on
all targets.

On 64-bit systems, this change has no practical effect on valid
user-space addresses because they do not use the sign bit (being
restricted to the lower half of the address space). However, using
unsigned comparison is the correct behavior for pointer arithmetic and
bounds checks.

Link: llvm#192355
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants