[llvm] [X86] Truncates i64 add arithmetic to i32 when known zeroes in upper 33 bits (PR #191619)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Apr 13 02:06:30 PDT 2026
https://github.com/2elliti updated https://github.com/llvm/llvm-project/pull/191619
>From 570b5156eb816729aa5c872ea847603695ec783f Mon Sep 17 00:00:00 2001
From: 2elliti <forstoic724321 at gmail.com>
Date: Sat, 11 Apr 2026 16:35:22 +0530
Subject: [PATCH 1/2] [X86] Truncates i64 add arithmetic to i32 when known
zeroes in upper 33 bits
---
llvm/lib/Target/X86/X86ISelLowering.cpp | 25 +++
llvm/test/CodeGen/X86/truncate-i64-add.ll | 178 ++++++++++++++++++++++
2 files changed, 203 insertions(+)
create mode 100644 llvm/test/CodeGen/X86/truncate-i64-add.ll
diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index 7ccfc412ff184..e2e6a5ad1d5e9 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -59402,6 +59402,31 @@ static SDValue combineAdd(SDNode *N, SelectionDAG &DAG,
if (SDValue IFMA52 = matchVPMADD52(N, DAG, DL, VT, Subtarget))
return IFMA52;
+ // If upper 33 bits of operands are 0, truncates opcode from i64 to i32.
+ if (VT == MVT::i64) {
+ APInt mask = APInt::getHighBitsSet(64, 33);
+ if (DAG.MaskedValueIsZero(Op0, mask) && DAG.MaskedValueIsZero(Op1, mask)) {
+ // Truncate operands MVT::i64 -> MVT::i32
+ SDValue X = DAG.getNode(ISD::TRUNCATE, DL, MVT::i32, Op0);
+ SDValue Y = DAG.getNode(ISD::TRUNCATE, DL, MVT::i32, Op1);
+
+ // now check for NUW and NSW
+ SDNodeFlags flags;
+ // No unsigned wrap, both operands has their upper 33bits 0, making their
+ // sum lower then max unsigned int32.
+ flags.setNoUnsignedWrap(true);
+ // Now check id node had NSW set true or false.
+ bool isNSW = N->getFlags().hasNoSignedWrap();
+
+ // Verify if new nodes has NSW.
+ isNSW = isNSW & DAG.willNotOverflowAdd(true, X, Y);
+ flags.setNoSignedWrap(isNSW);
+ auto addl = DAG.getNode(ISD::ADD, DL, MVT::i32, X, Y, flags);
+
+ return DAG.getNode(ISD::ZERO_EXTEND, DL, MVT::i64, addl);
+ }
+ }
+
return combineAddOrSubToADCOrSBB(N, DL, DAG);
}
diff --git a/llvm/test/CodeGen/X86/truncate-i64-add.ll b/llvm/test/CodeGen/X86/truncate-i64-add.ll
new file mode 100644
index 0000000000000..b51e0d954163d
--- /dev/null
+++ b/llvm/test/CodeGen/X86/truncate-i64-add.ll
@@ -0,0 +1,178 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc < %s -mtriple=i686-unknown-unknown | FileCheck %s --check-prefix=X86
+; RUN: llc < %s -mtriple=x86_64-linux | FileCheck %s --check-prefix=X64-LINUX
+; RUN: llc < %s -mtriple=x86_64-win32 | FileCheck %s --check-prefix=X64-WIN32
+
+; Both operands upper 33 bits are zero
+define i64 @test_add_i64_narrow(i64 %a, i64 %b) {
+; X86-LABEL: test_add_i64_narrow:
+; X86: # %bb.0:
+; X86-NEXT: movl $2147483647, %eax # imm = 0x7FFFFFFF
+; X86-NEXT: movl {{[0-9]+}}(%esp), %ecx
+; X86-NEXT: andl %eax, %ecx
+; X86-NEXT: andl {{[0-9]+}}(%esp), %eax
+; X86-NEXT: addl %ecx, %eax
+; X86-NEXT: xorl %edx, %edx
+; X86-NEXT: retl
+;
+; X64-LINUX-LABEL: test_add_i64_narrow:
+; X64-LINUX: # %bb.0:
+; X64-LINUX-NEXT: andl $2147483647, %edi # imm = 0x7FFFFFFF
+; X64-LINUX-NEXT: andl $2147483647, %esi # imm = 0x7FFFFFFF
+; X64-LINUX-NEXT: leal (%rsi,%rdi), %eax
+; X64-LINUX-NEXT: retq
+;
+; X64-WIN32-LABEL: test_add_i64_narrow:
+; X64-WIN32: # %bb.0:
+; X64-WIN32-NEXT: andl $2147483647, %ecx # imm = 0x7FFFFFFF
+; X64-WIN32-NEXT: andl $2147483647, %edx # imm = 0x7FFFFFFF
+; X64-WIN32-NEXT: leal (%rdx,%rcx), %eax
+; X64-WIN32-NEXT: retq
+ %a_masked = and i64 %a, 2147483647
+ %b_masked = and i64 %b, 2147483647
+ %res = add i64 %a_masked, %b_masked
+ ret i64 %res
+}
+
+; Both operands zero-extended from i16, should truncate to 32-bit add
+define i64 @add_zext_i16(i16 %a, i16 %b) nounwind {
+; X86-LABEL: add_zext_i16:
+; X86: # %bb.0:
+; X86-NEXT: movzwl {{[0-9]+}}(%esp), %ecx
+; X86-NEXT: movzwl {{[0-9]+}}(%esp), %eax
+; X86-NEXT: addl %ecx, %eax
+; X86-NEXT: xorl %edx, %edx
+; X86-NEXT: retl
+;
+; X64-LINUX-LABEL: add_zext_i16:
+; X64-LINUX: # %bb.0:
+; X64-LINUX-NEXT: movzwl %si, %ecx
+; X64-LINUX-NEXT: movzwl %di, %eax
+; X64-LINUX-NEXT: addl %ecx, %eax
+; X64-LINUX-NEXT: retq
+;
+; X64-WIN32-LABEL: add_zext_i16:
+; X64-WIN32: # %bb.0:
+; X64-WIN32-NEXT: movzwl %dx, %edx
+; X64-WIN32-NEXT: movzwl %cx, %eax
+; X64-WIN32-NEXT: addl %edx, %eax
+; X64-WIN32-NEXT: retq
+ %za = zext i16 %a to i64
+ %zb = zext i16 %b to i64
+ %sum = add nuw nsw i64 %za, %zb
+ ret i64 %sum
+}
+
+; One operand is a small constant
+define i64 @add_zext_i16_const(i16 %a) nounwind {
+; X86-LABEL: add_zext_i16_const:
+; X86: # %bb.0:
+; X86-NEXT: movzwl {{[0-9]+}}(%esp), %eax
+; X86-NEXT: addl $42, %eax
+; X86-NEXT: xorl %edx, %edx
+; X86-NEXT: retl
+;
+; X64-LINUX-LABEL: add_zext_i16_const:
+; X64-LINUX: # %bb.0:
+; X64-LINUX-NEXT: movzwl %di, %eax
+; X64-LINUX-NEXT: addl $42, %eax
+; X64-LINUX-NEXT: retq
+;
+; X64-WIN32-LABEL: add_zext_i16_const:
+; X64-WIN32: # %bb.0:
+; X64-WIN32-NEXT: movzwl %cx, %eax
+; X64-WIN32-NEXT: addl $42, %eax
+; X64-WIN32-NEXT: retq
+ %za = zext i16 %a to i64
+ %sum = add nuw nsw i64 %za, 42
+ ret i64 %sum
+}
+
+; Bit 32 set — must NOT truncate
+define i64 @add_bit32_set(i16 %a) nounwind {
+; X86-LABEL: add_bit32_set:
+; X86: # %bb.0:
+; X86-NEXT: movzwl {{[0-9]+}}(%esp), %eax
+; X86-NEXT: addl $42, %eax
+; X86-NEXT: movl $1, %edx
+; X86-NEXT: retl
+;
+; X64-LINUX-LABEL: add_bit32_set:
+; X64-LINUX: # %bb.0:
+; X64-LINUX-NEXT: movzwl %di, %ecx
+; X64-LINUX-NEXT: movabsq $4294967338, %rax # imm = 0x10000002A
+; X64-LINUX-NEXT: addq %rcx, %rax
+; X64-LINUX-NEXT: retq
+;
+; X64-WIN32-LABEL: add_bit32_set:
+; X64-WIN32: # %bb.0:
+; X64-WIN32-NEXT: movzwl %cx, %ecx
+; X64-WIN32-NEXT: movabsq $4294967338, %rax # imm = 0x10000002A
+; X64-WIN32-NEXT: addq %rcx, %rax
+; X64-WIN32-NEXT: retq
+ %za = zext i16 %a to i64
+ %or = or i64 %za, 4294967296
+ %sum = add nuw nsw i64 %or, 42
+ ret i64 %sum
+}
+
+; Sign-extended — must NOT truncate
+define i64 @add_sext_i16(i16 %a, i16 %b) nounwind {
+; X86-LABEL: add_sext_i16:
+; X86: # %bb.0:
+; X86-NEXT: movswl {{[0-9]+}}(%esp), %ecx
+; X86-NEXT: movl %ecx, %edx
+; X86-NEXT: sarl $31, %edx
+; X86-NEXT: movzwl {{[0-9]+}}(%esp), %eax
+; X86-NEXT: addl %ecx, %eax
+; X86-NEXT: adcl $0, %edx
+; X86-NEXT: retl
+;
+; X64-LINUX-LABEL: add_sext_i16:
+; X64-LINUX: # %bb.0:
+; X64-LINUX-NEXT: # kill: def $edi killed $edi def $rdi
+; X64-LINUX-NEXT: movswq %di, %rcx
+; X64-LINUX-NEXT: movzwl %si, %eax
+; X64-LINUX-NEXT: addq %rcx, %rax
+; X64-LINUX-NEXT: retq
+;
+; X64-WIN32-LABEL: add_sext_i16:
+; X64-WIN32: # %bb.0:
+; X64-WIN32-NEXT: movswq %cx, %rcx
+; X64-WIN32-NEXT: movzwl %dx, %eax
+; X64-WIN32-NEXT: addq %rcx, %rax
+; X64-WIN32-NEXT: retq
+ %sa = sext i16 %a to i64
+ %zb = zext i16 %b to i64
+ %sum = add nuw nsw i64 %sa, %zb
+ ret i64 %sum
+}
+
+; i8 source
+define i64 @test_i8_add(i8 %a, i8 %b) nounwind {
+; X86-LABEL: test_i8_add:
+; X86: # %bb.0:
+; X86-NEXT: movzbl {{[0-9]+}}(%esp), %ecx
+; X86-NEXT: movzbl {{[0-9]+}}(%esp), %eax
+; X86-NEXT: addl %ecx, %eax
+; X86-NEXT: xorl %edx, %edx
+; X86-NEXT: retl
+;
+; X64-LINUX-LABEL: test_i8_add:
+; X64-LINUX: # %bb.0:
+; X64-LINUX-NEXT: movzbl %sil, %ecx
+; X64-LINUX-NEXT: movzbl %dil, %eax
+; X64-LINUX-NEXT: addl %ecx, %eax
+; X64-LINUX-NEXT: retq
+;
+; X64-WIN32-LABEL: test_i8_add:
+; X64-WIN32: # %bb.0:
+; X64-WIN32-NEXT: movzbl %dl, %edx
+; X64-WIN32-NEXT: movzbl %cl, %eax
+; X64-WIN32-NEXT: addl %edx, %eax
+; X64-WIN32-NEXT: retq
+ %za = zext i8 %a to i64
+ %zb = zext i8 %b to i64
+ %sum = add i64 %za, %zb ; no nuw/nsw — should still fire
+ ret i64 %sum
+}
>From 4ac21a2f563c5431bd10c1e2651c48bbdb401064 Mon Sep 17 00:00:00 2001
From: 2elliti <forstoic724321 at gmail.com>
Date: Mon, 13 Apr 2026 00:44:38 +0530
Subject: [PATCH 2/2] Address review feedback: simplify wrap flags and update
tests
---
llvm/lib/Target/X86/X86ISelLowering.cpp | 14 +--
llvm/test/CodeGen/X86/truncate-i64-add.ll | 137 +++++++++-------------
2 files changed, 63 insertions(+), 88 deletions(-)
diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index e2e6a5ad1d5e9..15c7264f61249 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -59411,19 +59411,15 @@ static SDValue combineAdd(SDNode *N, SelectionDAG &DAG,
SDValue Y = DAG.getNode(ISD::TRUNCATE, DL, MVT::i32, Op1);
// now check for NUW and NSW
- SDNodeFlags flags;
+ SDNodeFlags Flags;
// No unsigned wrap, both operands has their upper 33bits 0, making their
// sum lower then max unsigned int32.
- flags.setNoUnsignedWrap(true);
- // Now check id node had NSW set true or false.
- bool isNSW = N->getFlags().hasNoSignedWrap();
+ Flags.setNoUnsignedWrap(true);
+ Flags.setNoSignedWrap(DAG.willNotOverflowAdd(true, X, Y));
- // Verify if new nodes has NSW.
- isNSW = isNSW & DAG.willNotOverflowAdd(true, X, Y);
- flags.setNoSignedWrap(isNSW);
- auto addl = DAG.getNode(ISD::ADD, DL, MVT::i32, X, Y, flags);
+ SDValue Add32 = DAG.getNode(ISD::ADD, DL, MVT::i32, X, Y, Flags);
- return DAG.getNode(ISD::ZERO_EXTEND, DL, MVT::i64, addl);
+ return DAG.getNode(ISD::ZERO_EXTEND, DL, MVT::i64, Add32);
}
}
diff --git a/llvm/test/CodeGen/X86/truncate-i64-add.ll b/llvm/test/CodeGen/X86/truncate-i64-add.ll
index b51e0d954163d..1ea5374b91694 100644
--- a/llvm/test/CodeGen/X86/truncate-i64-add.ll
+++ b/llvm/test/CodeGen/X86/truncate-i64-add.ll
@@ -1,7 +1,6 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
; RUN: llc < %s -mtriple=i686-unknown-unknown | FileCheck %s --check-prefix=X86
-; RUN: llc < %s -mtriple=x86_64-linux | FileCheck %s --check-prefix=X64-LINUX
-; RUN: llc < %s -mtriple=x86_64-win32 | FileCheck %s --check-prefix=X64-WIN32
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown | FileCheck %s --check-prefix=X64
; Both operands upper 33 bits are zero
define i64 @test_add_i64_narrow(i64 %a, i64 %b) {
@@ -15,19 +14,12 @@ define i64 @test_add_i64_narrow(i64 %a, i64 %b) {
; X86-NEXT: xorl %edx, %edx
; X86-NEXT: retl
;
-; X64-LINUX-LABEL: test_add_i64_narrow:
-; X64-LINUX: # %bb.0:
-; X64-LINUX-NEXT: andl $2147483647, %edi # imm = 0x7FFFFFFF
-; X64-LINUX-NEXT: andl $2147483647, %esi # imm = 0x7FFFFFFF
-; X64-LINUX-NEXT: leal (%rsi,%rdi), %eax
-; X64-LINUX-NEXT: retq
-;
-; X64-WIN32-LABEL: test_add_i64_narrow:
-; X64-WIN32: # %bb.0:
-; X64-WIN32-NEXT: andl $2147483647, %ecx # imm = 0x7FFFFFFF
-; X64-WIN32-NEXT: andl $2147483647, %edx # imm = 0x7FFFFFFF
-; X64-WIN32-NEXT: leal (%rdx,%rcx), %eax
-; X64-WIN32-NEXT: retq
+; X64-LABEL: test_add_i64_narrow:
+; X64: # %bb.0:
+; X64-NEXT: andl $2147483647, %edi # imm = 0x7FFFFFFF
+; X64-NEXT: andl $2147483647, %esi # imm = 0x7FFFFFFF
+; X64-NEXT: leal (%rsi,%rdi), %eax
+; X64-NEXT: retq
%a_masked = and i64 %a, 2147483647
%b_masked = and i64 %b, 2147483647
%res = add i64 %a_masked, %b_masked
@@ -44,19 +36,12 @@ define i64 @add_zext_i16(i16 %a, i16 %b) nounwind {
; X86-NEXT: xorl %edx, %edx
; X86-NEXT: retl
;
-; X64-LINUX-LABEL: add_zext_i16:
-; X64-LINUX: # %bb.0:
-; X64-LINUX-NEXT: movzwl %si, %ecx
-; X64-LINUX-NEXT: movzwl %di, %eax
-; X64-LINUX-NEXT: addl %ecx, %eax
-; X64-LINUX-NEXT: retq
-;
-; X64-WIN32-LABEL: add_zext_i16:
-; X64-WIN32: # %bb.0:
-; X64-WIN32-NEXT: movzwl %dx, %edx
-; X64-WIN32-NEXT: movzwl %cx, %eax
-; X64-WIN32-NEXT: addl %edx, %eax
-; X64-WIN32-NEXT: retq
+; X64-LABEL: add_zext_i16:
+; X64: # %bb.0:
+; X64-NEXT: movzwl %si, %ecx
+; X64-NEXT: movzwl %di, %eax
+; X64-NEXT: addl %ecx, %eax
+; X64-NEXT: retq
%za = zext i16 %a to i64
%zb = zext i16 %b to i64
%sum = add nuw nsw i64 %za, %zb
@@ -72,17 +57,11 @@ define i64 @add_zext_i16_const(i16 %a) nounwind {
; X86-NEXT: xorl %edx, %edx
; X86-NEXT: retl
;
-; X64-LINUX-LABEL: add_zext_i16_const:
-; X64-LINUX: # %bb.0:
-; X64-LINUX-NEXT: movzwl %di, %eax
-; X64-LINUX-NEXT: addl $42, %eax
-; X64-LINUX-NEXT: retq
-;
-; X64-WIN32-LABEL: add_zext_i16_const:
-; X64-WIN32: # %bb.0:
-; X64-WIN32-NEXT: movzwl %cx, %eax
-; X64-WIN32-NEXT: addl $42, %eax
-; X64-WIN32-NEXT: retq
+; X64-LABEL: add_zext_i16_const:
+; X64: # %bb.0:
+; X64-NEXT: movzwl %di, %eax
+; X64-NEXT: addl $42, %eax
+; X64-NEXT: retq
%za = zext i16 %a to i64
%sum = add nuw nsw i64 %za, 42
ret i64 %sum
@@ -97,19 +76,12 @@ define i64 @add_bit32_set(i16 %a) nounwind {
; X86-NEXT: movl $1, %edx
; X86-NEXT: retl
;
-; X64-LINUX-LABEL: add_bit32_set:
-; X64-LINUX: # %bb.0:
-; X64-LINUX-NEXT: movzwl %di, %ecx
-; X64-LINUX-NEXT: movabsq $4294967338, %rax # imm = 0x10000002A
-; X64-LINUX-NEXT: addq %rcx, %rax
-; X64-LINUX-NEXT: retq
-;
-; X64-WIN32-LABEL: add_bit32_set:
-; X64-WIN32: # %bb.0:
-; X64-WIN32-NEXT: movzwl %cx, %ecx
-; X64-WIN32-NEXT: movabsq $4294967338, %rax # imm = 0x10000002A
-; X64-WIN32-NEXT: addq %rcx, %rax
-; X64-WIN32-NEXT: retq
+; X64-LABEL: add_bit32_set:
+; X64: # %bb.0:
+; X64-NEXT: movzwl %di, %ecx
+; X64-NEXT: movabsq $4294967338, %rax # imm = 0x10000002A
+; X64-NEXT: addq %rcx, %rax
+; X64-NEXT: retq
%za = zext i16 %a to i64
%or = or i64 %za, 4294967296
%sum = add nuw nsw i64 %or, 42
@@ -128,20 +100,13 @@ define i64 @add_sext_i16(i16 %a, i16 %b) nounwind {
; X86-NEXT: adcl $0, %edx
; X86-NEXT: retl
;
-; X64-LINUX-LABEL: add_sext_i16:
-; X64-LINUX: # %bb.0:
-; X64-LINUX-NEXT: # kill: def $edi killed $edi def $rdi
-; X64-LINUX-NEXT: movswq %di, %rcx
-; X64-LINUX-NEXT: movzwl %si, %eax
-; X64-LINUX-NEXT: addq %rcx, %rax
-; X64-LINUX-NEXT: retq
-;
-; X64-WIN32-LABEL: add_sext_i16:
-; X64-WIN32: # %bb.0:
-; X64-WIN32-NEXT: movswq %cx, %rcx
-; X64-WIN32-NEXT: movzwl %dx, %eax
-; X64-WIN32-NEXT: addq %rcx, %rax
-; X64-WIN32-NEXT: retq
+; X64-LABEL: add_sext_i16:
+; X64: # %bb.0:
+; X64-NEXT: # kill: def $edi killed $edi def $rdi
+; X64-NEXT: movswq %di, %rcx
+; X64-NEXT: movzwl %si, %eax
+; X64-NEXT: addq %rcx, %rax
+; X64-NEXT: retq
%sa = sext i16 %a to i64
%zb = zext i16 %b to i64
%sum = add nuw nsw i64 %sa, %zb
@@ -158,21 +123,35 @@ define i64 @test_i8_add(i8 %a, i8 %b) nounwind {
; X86-NEXT: xorl %edx, %edx
; X86-NEXT: retl
;
-; X64-LINUX-LABEL: test_i8_add:
-; X64-LINUX: # %bb.0:
-; X64-LINUX-NEXT: movzbl %sil, %ecx
-; X64-LINUX-NEXT: movzbl %dil, %eax
-; X64-LINUX-NEXT: addl %ecx, %eax
-; X64-LINUX-NEXT: retq
-;
-; X64-WIN32-LABEL: test_i8_add:
-; X64-WIN32: # %bb.0:
-; X64-WIN32-NEXT: movzbl %dl, %edx
-; X64-WIN32-NEXT: movzbl %cl, %eax
-; X64-WIN32-NEXT: addl %edx, %eax
-; X64-WIN32-NEXT: retq
+; X64-LABEL: test_i8_add:
+; X64: # %bb.0:
+; X64-NEXT: movzbl %sil, %ecx
+; X64-NEXT: movzbl %dil, %eax
+; X64-NEXT: addl %ecx, %eax
+; X64-NEXT: retq
%za = zext i8 %a to i64
%zb = zext i8 %b to i64
%sum = add i64 %za, %zb ; no nuw/nsw — should still fire
ret i64 %sum
}
+
+define i64 @add_zext_i32(i32 %a, i32 %b) {
+; X86-LABEL: add_zext_i32:
+; X86: # %bb.0:
+; X86-NEXT: movl {{[0-9]+}}(%esp), %eax
+; X86-NEXT: xorl %edx, %edx
+; X86-NEXT: addl {{[0-9]+}}(%esp), %eax
+; X86-NEXT: setb %dl
+; X86-NEXT: retl
+;
+; X64-LABEL: add_zext_i32:
+; X64: # %bb.0:
+; X64-NEXT: movl %edi, %ecx
+; X64-NEXT: movl %esi, %eax
+; X64-NEXT: addq %rcx, %rax
+; X64-NEXT: retq
+ %za = zext i32 %a to i64
+ %zb = zext i32 %b to i64
+ %sum = add i64 %za, %zb
+ ret i64 %sum
+}
More information about the llvm-commits
mailing list