[llvm] [X86] Truncates i64 add arithmetic to i32 when known zeroes in upper 33 bits (PR #191619)

Takashi Idobe via llvm-commits llvm-commits at lists.llvm.org
Sat Apr 11 10:47:51 PDT 2026


================
@@ -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
+}
----------------
Takashiidobe wrote:

I'd add a test like this:

```llvm
define i64 @add_zext_i32(i32 %a, i32 %b) {
  %za = zext i32 %a to i64
  %zb = zext i32 %b to i64
  %sum = add i64 %za, %zb
  ret i64 %sum
}
```

Since this shows where bit 31 could be set (%a and %b are i32s so their 32nd bit can be set), so this shouldn't fold, and it doesn't and emits addq. 



https://github.com/llvm/llvm-project/pull/191619


More information about the llvm-commits mailing list