[llvm] [CodeGen][RISCV][AArch64] Added signed extension support to TypePromotion (PR #226304)
Ananth Jasty via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 24 14:52:13 PDT 2026
https://github.com/bbbill42 updated https://github.com/llvm/llvm-project/pull/226304
>From 2fa4034b97f48bd15c505a1cdec9445c8a9c73db Mon Sep 17 00:00:00 2001
From: Ananth Jasty <ananth at openrv64.org>
Date: Thu, 24 Sep 2026 20:19:55 +0000
Subject: [PATCH 1/9] [CodeGen][RISCV][AArch64] Test coverage for TypePromotion
"before" Signed extension.
Assisted-by: Codex
---
.../AArch64/typepromotion-zext-nneg.ll | 51 ++
.../CodeGen/RISCV/typepromotion-zext-nneg.ll | 50 ++
.../TypePromotion/AArch64/phi-zext-nneg.ll | 764 ++++++++++++++++++
3 files changed, 865 insertions(+)
create mode 100644 llvm/test/CodeGen/AArch64/typepromotion-zext-nneg.ll
create mode 100644 llvm/test/CodeGen/RISCV/typepromotion-zext-nneg.ll
create mode 100644 llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg.ll
diff --git a/llvm/test/CodeGen/AArch64/typepromotion-zext-nneg.ll b/llvm/test/CodeGen/AArch64/typepromotion-zext-nneg.ll
new file mode 100644
index 0000000000000..3a2f25d918382
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/typepromotion-zext-nneg.ll
@@ -0,0 +1,51 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc -mtriple=aarch64 -verify-machineinstrs < %s | FileCheck %s
+
+; A signed halfword edge index uses negative values as an end sentinel.
+; Both the entry and backedge checks guard the nonnegative index use.
+; FIXME: Promote the index PHI with sign extension to avoid redundant masks.
+
+define i32 @edge_sum(ptr %edges, i16 signext %head) {
+; CHECK-LABEL: edge_sum:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: // kill: def $w1 killed $w1 def $x1
+; CHECK-NEXT: tbnz w1, #31, .LBB0_4
+; CHECK-NEXT: // %bb.1: // %body.preheader
+; CHECK-NEXT: mov x8, x0
+; CHECK-NEXT: mov w0, wzr
+; CHECK-NEXT: and x9, x1, #0xffff
+; CHECK-NEXT: .LBB0_2: // %body
+; CHECK-NEXT: // =>This Inner Loop Header: Depth=1
+; CHECK-NEXT: add x9, x8, x9, lsl #3
+; CHECK-NEXT: ldrsh w10, [x9, #4]
+; CHECK-NEXT: ldrsh w11, [x9, #2]
+; CHECK-NEXT: add w0, w0, w10
+; CHECK-NEXT: and x9, x11, #0xffff
+; CHECK-NEXT: tbz w11, #31, .LBB0_2
+; CHECK-NEXT: // %bb.3: // %exit
+; CHECK-NEXT: ret
+; CHECK-NEXT: .LBB0_4:
+; CHECK-NEXT: mov w0, wzr
+; CHECK-NEXT: ret
+entry:
+ %ok = icmp sge i16 %head, 0
+ br i1 %ok, label %body, label %exit
+
+body:
+ %idx = phi i16 [ %head, %entry ], [ %next, %body ]
+ %sum = phi i32 [ 0, %entry ], [ %sum.next, %body ]
+ %wide = zext nneg i16 %idx to i64
+ %address = getelementptr [8 x i8], ptr %edges, i64 %wide
+ %next.ptr = getelementptr i8, ptr %address, i64 2
+ %value.ptr = getelementptr i8, ptr %address, i64 4
+ %value = load i16, ptr %value.ptr, align 2
+ %value.wide = sext i16 %value to i32
+ %sum.next = add i32 %sum, %value.wide
+ %next = load i16, ptr %next.ptr, align 2
+ %continue = icmp sge i16 %next, 0
+ br i1 %continue, label %body, label %exit
+
+exit:
+ %ret = phi i32 [ 0, %entry ], [ %sum.next, %body ]
+ ret i32 %ret
+}
diff --git a/llvm/test/CodeGen/RISCV/typepromotion-zext-nneg.ll b/llvm/test/CodeGen/RISCV/typepromotion-zext-nneg.ll
new file mode 100644
index 0000000000000..58f413b51a977
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/typepromotion-zext-nneg.ll
@@ -0,0 +1,50 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc -mtriple=riscv64 -mattr=+zba,+zbb -verify-machineinstrs < %s | FileCheck %s
+
+; A signed halfword edge index uses negative values as an end sentinel.
+; Both the entry and backedge checks guard the nonnegative index use.
+; FIXME: Promote the index PHI with sign extension to avoid redundant masks.
+
+define i32 @edge_sum(ptr %edges, i16 signext %head) {
+; CHECK-LABEL: edge_sum:
+; CHECK: # %bb.0: # %entry
+; CHECK-NEXT: bltz a1, .LBB0_4
+; CHECK-NEXT: # %bb.1: # %body.preheader
+; CHECK-NEXT: mv a2, a0
+; CHECK-NEXT: li a0, 0
+; CHECK-NEXT: zext.h a1, a1
+; CHECK-NEXT: .LBB0_2: # %body
+; CHECK-NEXT: # =>This Inner Loop Header: Depth=1
+; CHECK-NEXT: sh3add a1, a1, a2
+; CHECK-NEXT: lh a3, 2(a1)
+; CHECK-NEXT: lh a1, 4(a1)
+; CHECK-NEXT: addw a0, a0, a1
+; CHECK-NEXT: zext.h a1, a3
+; CHECK-NEXT: bgez a3, .LBB0_2
+; CHECK-NEXT: # %bb.3: # %exit
+; CHECK-NEXT: ret
+; CHECK-NEXT: .LBB0_4:
+; CHECK-NEXT: li a0, 0
+; CHECK-NEXT: ret
+entry:
+ %ok = icmp sge i16 %head, 0
+ br i1 %ok, label %body, label %exit
+
+body:
+ %idx = phi i16 [ %head, %entry ], [ %next, %body ]
+ %sum = phi i32 [ 0, %entry ], [ %sum.next, %body ]
+ %wide = zext nneg i16 %idx to i64
+ %address = getelementptr [8 x i8], ptr %edges, i64 %wide
+ %next.ptr = getelementptr i8, ptr %address, i64 2
+ %value.ptr = getelementptr i8, ptr %address, i64 4
+ %value = load i16, ptr %value.ptr, align 2
+ %value.wide = sext i16 %value to i32
+ %sum.next = add i32 %sum, %value.wide
+ %next = load i16, ptr %next.ptr, align 2
+ %continue = icmp sge i16 %next, 0
+ br i1 %continue, label %body, label %exit
+
+exit:
+ %ret = phi i32 [ 0, %entry ], [ %sum.next, %body ]
+ ret i32 %ret
+}
diff --git a/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg.ll b/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg.ll
new file mode 100644
index 0000000000000..5efc97922f5ee
--- /dev/null
+++ b/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg.ll
@@ -0,0 +1,764 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -mtriple=aarch64 -passes=typepromotion,verify -S < %s | FileCheck %s
+
+; The nonnegative use of the loop PHI can use sign-extended sources.
+define i32 @edge_sum(ptr %edges, i16 signext %head) {
+; CHECK-LABEL: define i32 @edge_sum(
+; CHECK-SAME: ptr [[EDGES:%.*]], i16 signext [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[TMP0]] to i16
+; CHECK-NEXT: [[OK:%.*]] = icmp sge i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[OK]], label %[[BODY:.*]], label %[[EXIT:.*]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ [[TMP2:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[ADDRESS:%.*]] = getelementptr [8 x i8], ptr [[EDGES]], i64 [[IDX]]
+; CHECK-NEXT: [[NEXT_PTR:%.*]] = getelementptr i8, ptr [[ADDRESS]], i64 2
+; CHECK-NEXT: [[VALUE_PTR:%.*]] = getelementptr i8, ptr [[ADDRESS]], i64 4
+; CHECK-NEXT: [[VALUE:%.*]] = load i16, ptr [[VALUE_PTR]], align 2
+; CHECK-NEXT: [[VALUE_WIDE:%.*]] = sext i16 [[VALUE]] to i32
+; CHECK-NEXT: [[SUM_NEXT]] = add i32 [[SUM]], [[VALUE_WIDE]]
+; CHECK-NEXT: [[NEXT:%.*]] = load i16, ptr [[NEXT_PTR]], align 2
+; CHECK-NEXT: [[TMP2]] = zext i16 [[NEXT]] to i64
+; CHECK-NEXT: [[TMP3:%.*]] = trunc i64 [[TMP2]] to i16
+; CHECK-NEXT: [[CONTINUE:%.*]] = icmp sge i16 [[TMP3]], 0
+; CHECK-NEXT: br i1 [[CONTINUE]], label %[[BODY]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RET:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i32 [[RET]]
+;
+entry:
+ %ok = icmp sge i16 %head, 0
+ br i1 %ok, label %body, label %exit
+
+body:
+ %idx = phi i16 [ %head, %entry ], [ %next, %body ]
+ %sum = phi i32 [ 0, %entry ], [ %sum.next, %body ]
+ %wide = zext nneg i16 %idx to i64
+ %address = getelementptr [8 x i8], ptr %edges, i64 %wide
+ %next.ptr = getelementptr i8, ptr %address, i64 2
+ %value.ptr = getelementptr i8, ptr %address, i64 4
+ %value = load i16, ptr %value.ptr, align 2
+ %value.wide = sext i16 %value to i32
+ %sum.next = add i32 %sum, %value.wide
+ %next = load i16, ptr %next.ptr, align 2
+ %continue = icmp sge i16 %next, 0
+ br i1 %continue, label %body, label %exit
+
+exit:
+ %ret = phi i32 [ 0, %entry ], [ %sum.next, %body ]
+ ret i32 %ret
+}
+
+; Without nneg, preserve unsigned promotion.
+define i32 @edge_sum_unsigned(ptr %edges, i16 signext %head) {
+; CHECK-LABEL: define i32 @edge_sum_unsigned(
+; CHECK-SAME: ptr [[EDGES:%.*]], i16 signext [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[TMP0]] to i16
+; CHECK-NEXT: [[OK:%.*]] = icmp sge i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[OK]], label %[[BODY:.*]], label %[[EXIT:.*]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ [[TMP2:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[ADDRESS:%.*]] = getelementptr [8 x i8], ptr [[EDGES]], i64 [[IDX]]
+; CHECK-NEXT: [[NEXT_PTR:%.*]] = getelementptr i8, ptr [[ADDRESS]], i64 2
+; CHECK-NEXT: [[VALUE_PTR:%.*]] = getelementptr i8, ptr [[ADDRESS]], i64 4
+; CHECK-NEXT: [[VALUE:%.*]] = load i16, ptr [[VALUE_PTR]], align 2
+; CHECK-NEXT: [[VALUE_WIDE:%.*]] = sext i16 [[VALUE]] to i32
+; CHECK-NEXT: [[SUM_NEXT]] = add i32 [[SUM]], [[VALUE_WIDE]]
+; CHECK-NEXT: [[NEXT:%.*]] = load i16, ptr [[NEXT_PTR]], align 2
+; CHECK-NEXT: [[TMP2]] = zext i16 [[NEXT]] to i64
+; CHECK-NEXT: [[TMP3:%.*]] = trunc i64 [[TMP2]] to i16
+; CHECK-NEXT: [[CONTINUE:%.*]] = icmp sge i16 [[TMP3]], 0
+; CHECK-NEXT: br i1 [[CONTINUE]], label %[[BODY]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RET:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i32 [[RET]]
+;
+entry:
+ %ok = icmp sge i16 %head, 0
+ br i1 %ok, label %body, label %exit
+
+body:
+ %idx = phi i16 [ %head, %entry ], [ %next, %body ]
+ %sum = phi i32 [ 0, %entry ], [ %sum.next, %body ]
+ %wide = zext i16 %idx to i64
+ %address = getelementptr [8 x i8], ptr %edges, i64 %wide
+ %next.ptr = getelementptr i8, ptr %address, i64 2
+ %value.ptr = getelementptr i8, ptr %address, i64 4
+ %value = load i16, ptr %value.ptr, align 2
+ %value.wide = sext i16 %value to i32
+ %sum.next = add i32 %sum, %value.wide
+ %next = load i16, ptr %next.ptr, align 2
+ %continue = icmp sge i16 %next, 0
+ br i1 %continue, label %body, label %exit
+
+exit:
+ %ret = phi i32 [ 0, %entry ], [ %sum.next, %body ]
+ ret i32 %ret
+}
+
+; An unsigned use outside the guarded loop must preserve all 16 input bits,
+; including when a negative input bypasses the nneg extension entirely.
+define i64 @mixed_extensions(i16 %head) {
+; CHECK-LABEL: define i64 @mixed_extensions(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[UNSIGNED:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[TMP0]] to i16
+; CHECK-NEXT: [[OK:%.*]] = icmp sge i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[OK]], label %[[LOOP:.*]], label %[[EXIT:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[LOOP]] ]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i64 [[UNSIGNED]]
+;
+entry:
+ %unsigned = zext i16 %head to i64
+ %ok = icmp sge i16 %head, 0
+ br i1 %ok, label %loop, label %exit
+
+loop:
+ %idx = phi i16 [ %head, %entry ], [ 0, %loop ]
+ %wide = zext nneg i16 %idx to i64
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ ret i64 %unsigned
+}
+
+; The constant in the comparison must use the same extension as %head.
+; Negative inputs never execute the nneg extension.
+define i1 @negative_constant(i16 %head) {
+; CHECK-LABEL: define i1 @negative_constant(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[MATCH:%.*]] = icmp eq i64 [[TMP0]], 65535
+; CHECK-NEXT: ret i1 [[MATCH]]
+;
+entry:
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %head, %entry ], [ 0, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %match = icmp eq i16 %head, -1
+ ret i1 %match
+}
+
+; A negative PHI incoming constant is allowed: it exits before the nneg use.
+define i64 @negative_phi_constant(i16 %head) {
+; CHECK-LABEL: define i64 @negative_phi_constant(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 65535, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IDX]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: br label %[[LOOP]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i64 [[SUM]]
+;
+entry:
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %head, %entry ], [ -1, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %wide, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ br label %loop
+
+exit:
+ ret i64 %sum
+}
+
+; Switch case constants must match the representation of the widened value.
+define i32 @negative_switch_case(i16 %head) {
+; CHECK-LABEL: define i32 @negative_switch_case(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: switch i64 [[TMP0]], label %[[OTHER:.*]] [
+; CHECK-NEXT: i64 65535, label %[[MINUS_ONE:.*]]
+; CHECK-NEXT: ]
+; CHECK: [[MINUS_ONE]]:
+; CHECK-NEXT: ret i32 1
+; CHECK: [[OTHER]]:
+; CHECK-NEXT: ret i32 0
+;
+entry:
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %head, %entry ], [ 0, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ switch i16 %head, label %other [ i16 -1, label %minus_one ]
+
+minus_one:
+ ret i32 1
+
+other:
+ ret i32 0
+}
+
+; Unsigned operations must not be widened using sign-extended operands.
+define i64 @phi_lshr(i16 %head) {
+; CHECK-LABEL: define i64 @phi_lshr(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[INITIAL:%.*]] = lshr i64 [[TMP0]], 1
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ %initial = lshr i16 %head, 1
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; Unsigned operations must not be widened using sign-extended operands.
+define i64 @phi_udiv(i16 %head) {
+; CHECK-LABEL: define i64 @phi_udiv(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[INITIAL:%.*]] = udiv i64 [[TMP0]], 3
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ %initial = udiv i16 %head, 3
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; Unsigned operations must not be widened using sign-extended operands.
+define i64 @phi_urem(i16 %head) {
+; CHECK-LABEL: define i64 @phi_urem(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[INITIAL:%.*]] = urem i64 [[TMP0]], 7
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ %initial = urem i16 %head, 7
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; Signed promotion requires nsw; nuw alone does not establish signed safety.
+define i64 @phi_add_nsw(i16 %head) {
+; CHECK-LABEL: define i64 @phi_add_nsw(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[INITIAL:%.*]] = add nsw i16 [[HEAD]], 1
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[IDX]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[WIDE:%.*]] = zext nneg i16 [[IDX]] to i64
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[WIDE]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[WIDE]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ %initial = add nsw i16 %head, 1
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; Signed promotion requires nsw; nuw alone does not establish signed safety.
+define i64 @phi_add_nuw(i16 %head) {
+; CHECK-LABEL: define i64 @phi_add_nuw(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[INITIAL:%.*]] = add nuw i64 [[TMP0]], 1
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ %initial = add nuw i16 %head, 1
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; Signed promotion requires nsw; nuw alone does not establish signed safety.
+define i64 @phi_sub_nsw(i16 %head) {
+; CHECK-LABEL: define i64 @phi_sub_nsw(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[INITIAL:%.*]] = sub nsw i16 [[HEAD]], 1
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[IDX]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[WIDE:%.*]] = zext nneg i16 [[IDX]] to i64
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[WIDE]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[WIDE]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ %initial = sub nsw i16 %head, 1
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; Signed promotion requires nsw; nuw alone does not establish signed safety.
+define i64 @phi_sub_nuw(i16 %head) {
+; CHECK-LABEL: define i64 @phi_sub_nuw(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[INITIAL:%.*]] = sub nuw i64 [[TMP0]], 1
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ %initial = sub nuw i16 %head, 1
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; Signed promotion requires nsw; nuw alone does not establish signed safety.
+define i64 @phi_mul_nsw(i16 %head) {
+; CHECK-LABEL: define i64 @phi_mul_nsw(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[INITIAL:%.*]] = mul nsw i16 [[HEAD]], 2
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[IDX]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[WIDE:%.*]] = zext nneg i16 [[IDX]] to i64
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[WIDE]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[WIDE]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ %initial = mul nsw i16 %head, 2
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; Signed promotion requires nsw; nuw alone does not establish signed safety.
+define i64 @phi_mul_nuw(i16 %head) {
+; CHECK-LABEL: define i64 @phi_mul_nuw(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[INITIAL:%.*]] = mul nuw i64 [[TMP0]], 2
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ %initial = mul nuw i16 %head, 2
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; Signed promotion requires nsw; nuw alone does not establish signed safety.
+define i64 @phi_shl_nsw(i16 %head) {
+; CHECK-LABEL: define i64 @phi_shl_nsw(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[INITIAL:%.*]] = shl nsw i16 [[HEAD]], 1
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[IDX]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[WIDE:%.*]] = zext nneg i16 [[IDX]] to i64
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[WIDE]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[WIDE]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ %initial = shl nsw i16 %head, 1
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; Signed promotion requires nsw; nuw alone does not establish signed safety.
+define i64 @phi_shl_nuw(i16 %head) {
+; CHECK-LABEL: define i64 @phi_shl_nuw(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[INITIAL:%.*]] = shl nuw i64 [[TMP0]], 1
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ %initial = shl nuw i16 %head, 1
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; The wrapping range-check idiom has an unsigned promotion rule. It must
+; not provide a fallback for signed promotion of this source's use graph.
+define i1 @unsigned_wrap(i16 %head) {
+; CHECK-LABEL: define i1 @unsigned_wrap(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[OFFSET:%.*]] = add i64 [[TMP0]], -2
+; CHECK-NEXT: [[IN_RANGE:%.*]] = icmp ule i64 [[OFFSET]], 65533
+; CHECK-NEXT: ret i1 [[IN_RANGE]]
+;
+entry:
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %head, %entry ], [ 0, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %offset = add i16 %head, -2
+ %in.range = icmp ule i16 %offset, -3
+ ret i1 %in.range
+}
>From 483662d677fd5067051a58fc4af46784c42bf58f Mon Sep 17 00:00:00 2001
From: Ananth Jasty <ananth at openrv64.org>
Date: Thu, 24 Sep 2026 00:32:40 +0000
Subject: [PATCH 2/9] [RISCV] Preliminary patch for TypePromotion handling of
sext for zext nneg.
---
llvm/lib/CodeGen/TypePromotion.cpp | 23 ++++++++++++++---------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git a/llvm/lib/CodeGen/TypePromotion.cpp b/llvm/lib/CodeGen/TypePromotion.cpp
index 2736ff3f8a299..13e95bb46fc93 100644
--- a/llvm/lib/CodeGen/TypePromotion.cpp
+++ b/llvm/lib/CodeGen/TypePromotion.cpp
@@ -113,6 +113,7 @@ class IRPromoter {
SmallPtrSet<Value *, 8> NewInsts;
DenseMap<Value *, SmallVector<Type *, 4>> TruncTysMap;
SmallPtrSet<Value *, 8> Promoted;
+ bool UseSExt;
void ReplaceAllUsersOfWith(Value *From, Value *To);
void ExtendSources();
@@ -125,9 +126,10 @@ class IRPromoter {
IRPromoter(LLVMContext &C, unsigned Width, SetVector<Value *> &visited,
SetVector<Value *> &sources, SetVector<Instruction *> &sinks,
SmallPtrSetImpl<Instruction *> &wrap,
- SmallPtrSetImpl<Instruction *> &instsToRemove)
+ SmallPtrSetImpl<Instruction *> &instsToRemove,
+ bool useSExt = false)
: Ctx(C), PromotedWidth(Width), Visited(visited), Sources(sources),
- Sinks(sinks), SafeWrap(wrap), InstsToRemove(instsToRemove) {
+ Sinks(sinks), SafeWrap(wrap), InstsToRemove(instsToRemove), UseSExt(useSExt) {
ExtTy = IntegerType::get(Ctx, PromotedWidth);
}
@@ -172,7 +174,8 @@ class TypePromotionImpl {
// Is V an instruction thats result can trivially promoted, or has safe
// wrapping.
bool isLegalToPromote(Value *V);
- bool TryToPromote(Value *V, unsigned PromotedWidth, const LoopInfo &LI);
+ bool TryToPromote(Value *V, unsigned PromotedWidth, const LoopInfo &LI,
+ bool UseSExt = false);
public:
bool run(Function &F, const TargetMachine *TM,
@@ -453,8 +456,8 @@ void IRPromoter::ExtendSources() {
if (auto *I = dyn_cast<Instruction>(V))
Builder.SetCurrentDebugLocation(I->getDebugLoc());
- Value *ZExt = Builder.CreateZExt(V, ExtTy);
- if (auto *I = dyn_cast<Instruction>(ZExt)) {
+ Value *Ext = UseSExt ? Builder.CreateSExt(V, ExtTy) : Builder.CreateZExt(V, ExtTy);
+ if (auto *I = dyn_cast<Instruction>(Ext)) {
if (isa<Argument>(V))
I->moveBefore(InsertPt);
else
@@ -462,7 +465,7 @@ void IRPromoter::ExtendSources() {
NewInsts.insert(I);
}
- ReplaceAllUsersOfWith(V, ZExt);
+ ReplaceAllUsersOfWith(V, Ext);
};
// Now, insert extending instructions between the sources and their users.
@@ -814,7 +817,7 @@ bool TypePromotionImpl::isLegalToPromote(Value *V) {
}
bool TypePromotionImpl::TryToPromote(Value *V, unsigned PromotedWidth,
- const LoopInfo &LI) {
+ const LoopInfo &LI, bool UseSExt) {
Type *OrigTy = V->getType();
TypeSize = OrigTy->getPrimitiveSizeInBits().getFixedValue();
SafeToPromote.clear();
@@ -942,7 +945,7 @@ bool TypePromotionImpl::TryToPromote(Value *V, unsigned PromotedWidth,
return false;
IRPromoter Promoter(*Ctx, PromotedWidth, CurrentVisited, Sources, Sinks,
- SafeWrap, InstsToRemove);
+ SafeWrap, InstsToRemove, UseSExt);
Promoter.Mutate();
return true;
}
@@ -1008,6 +1011,8 @@ bool TypePromotionImpl::run(Function &F, const TargetMachine *TM,
isa<IntegerType>(I.getType()) && BBIsInLoop(&BB)) {
LLVM_DEBUG(dbgs() << "IR Promotion: Searching from: "
<< *I.getOperand(0) << "\n");
+ auto *ZExt = cast<ZExtInst>(&I);
+ bool UseSExt = ZExt->hasNonNeg();
EVT ZExtVT = TLI->getValueType(DL, I.getType());
Instruction *Phi = static_cast<Instruction *>(I.getOperand(0));
auto PromoteWidth = ZExtVT.getFixedSizeInBits();
@@ -1016,7 +1021,7 @@ bool TypePromotionImpl::run(Function &F, const TargetMachine *TM,
<< "register for ZExt type\n");
continue;
}
- MadeChange |= TryToPromote(Phi, PromoteWidth, LI);
+ MadeChange |= TryToPromote(Phi, PromoteWidth, LI, UseSExt);
} else if (auto *ICmp = dyn_cast<ICmpInst>(&I)) {
// Search up from icmps to try to promote their operands.
// Skip signed or pointer compares
>From 826f57ae78166d1f761c2f3931e3b30142c3392b Mon Sep 17 00:00:00 2001
From: Ananth Jasty <ananth at openrv64.org>
Date: Thu, 24 Sep 2026 13:08:58 +0000
Subject: [PATCH 3/9] [CodeGen] TypePromotion now tests signed matching before
cleaning up zext/sext-trunc pairs correctly.
---
llvm/lib/CodeGen/TypePromotion.cpp | 49 ++++++++++++++++++++----------
1 file changed, 33 insertions(+), 16 deletions(-)
diff --git a/llvm/lib/CodeGen/TypePromotion.cpp b/llvm/lib/CodeGen/TypePromotion.cpp
index 13e95bb46fc93..f6e6ddd1093d9 100644
--- a/llvm/lib/CodeGen/TypePromotion.cpp
+++ b/llvm/lib/CodeGen/TypePromotion.cpp
@@ -122,6 +122,8 @@ class IRPromoter {
void TruncateSinks();
void Cleanup();
+ bool CheckSignedMatch(const Value *V) const;
+
public:
IRPromoter(LLVMContext &C, unsigned Width, SetVector<Value *> &visited,
SetVector<Value *> &sources, SetVector<Instruction *> &sinks,
@@ -272,6 +274,8 @@ bool TypePromotionImpl::isSink(Value *V) {
return LessOrEqualTypeSize(Return->getReturnValue());
if (auto *ZExt = dyn_cast<ZExtInst>(V))
return GreaterThanTypeSize(ZExt);
+ if (auto *SExt = dyn_cast<SExtInst>(V))
+ return GreaterThanTypeSize(SExt);
if (auto *Switch = dyn_cast<SwitchInst>(V))
return LessThanTypeSize(Switch->getCondition());
if (auto *ICmp = dyn_cast<ICmpInst>(V))
@@ -604,15 +608,14 @@ void IRPromoter::TruncateSinks() {
continue;
}
- // Don't insert a trunc for a zext which can still legally promote.
+ // Don't insert a trunc for a (z/s)ext which can still legally promote.
// Nor insert a trunc when the input value to that trunc has the same width
// as the zext we are inserting it for. When this happens the input operand
- // for the zext will be promoted to the same width as the zext's return type
- // rendering that zext unnecessary. This zext gets removed before the end
+ // for the zext will be promoted to the same width as the ext's return type
+ // rendering that ext unnecessary. This zext gets removed before the end
// of the pass.
- if (auto ZExt = dyn_cast<ZExtInst>(I))
- if (ZExt->getType()->getScalarSizeInBits() >= PromotedWidth)
- continue;
+ if (CheckSignedMatch(I) && (I->getType()->getScalarSizeInBits() >= PromotedWidth))
+ continue;
// Now handle the others.
for (unsigned i = 0; i < I->getNumOperands(); ++i) {
@@ -627,31 +630,34 @@ void IRPromoter::TruncateSinks() {
void IRPromoter::Cleanup() {
LLVM_DEBUG(dbgs() << "IR Promotion: Cleanup..\n");
- // Some zexts will now have become redundant, along with their trunc
+ // Some (z/s)exts will now have become redundant, along with their trunc
// operands, so remove them.
for (auto *V : Visited) {
- if (!isa<ZExtInst>(V))
+ if (!isa<ZExtInst>(V) && !isa<SExtInst>(V))
+ continue;
+
+ if (!CheckSignedMatch(V))
continue;
- auto ZExt = cast<ZExtInst>(V);
- if (ZExt->getDestTy() != ExtTy)
+ auto XExt = cast<CastInst>(V);
+ if (XExt->getDestTy() != ExtTy)
continue;
- Value *Src = ZExt->getOperand(0);
- if (ZExt->getSrcTy() == ZExt->getDestTy()) {
- LLVM_DEBUG(dbgs() << "IR Promotion: Removing unnecessary cast: " << *ZExt
+ Value *Src = XExt->getOperand(0);
+ if (XExt->getSrcTy() == XExt->getDestTy()) {
+ LLVM_DEBUG(dbgs() << "IR Promotion: Removing unnecessary cast: " << *XExt
<< "\n");
- ReplaceAllUsersOfWith(ZExt, Src);
+ ReplaceAllUsersOfWith(XExt, Src);
continue;
}
- // We've inserted a trunc for a zext sink, but we already know that the
+ // We've inserted a trunc for a (z/s)ext sink, but we already know that the
// input is in range, negating the need for the trunc.
if (NewInsts.count(Src) && isa<TruncInst>(Src)) {
auto *Trunc = cast<TruncInst>(Src);
assert(Trunc->getOperand(0)->getType() == ExtTy &&
"expected inserted trunc to be operating on i32");
- ReplaceAllUsersOfWith(ZExt, Trunc->getOperand(0));
+ ReplaceAllUsersOfWith(XExt, Trunc->getOperand(0));
}
}
@@ -730,6 +736,17 @@ void IRPromoter::Mutate() {
LLVM_DEBUG(dbgs() << "IR Promotion: Mutation complete\n");
}
+bool IRPromoter::CheckSignedMatch(const Value *V) const {
+ bool SignedMatch = false;
+
+ if (isa<SExtInst>(V))
+ SignedMatch = UseSExt;
+ if (auto *ZExt = dyn_cast<ZExtInst>(V))
+ SignedMatch = !UseSExt || ZExt->hasNonNeg();
+
+ return SignedMatch;
+}
+
/// We disallow booleans to make life easier when dealing with icmps but allow
/// any other integer that fits in a scalar register. Void types are accepted
/// so we can handle switches.
>From 03e0a67292f81448dc0855ff6c50a7ff8bdce7b3 Mon Sep 17 00:00:00 2001
From: Ananth Jasty <ananth at openrv64.org>
Date: Thu, 24 Sep 2026 13:33:06 +0000
Subject: [PATCH 4/9] [CodeGen] Fixed lshr and constant handling for SExt
promotion. Might still have an issue with other insts.
---
llvm/lib/CodeGen/TypePromotion.cpp | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/llvm/lib/CodeGen/TypePromotion.cpp b/llvm/lib/CodeGen/TypePromotion.cpp
index f6e6ddd1093d9..243cc00c49c7d 100644
--- a/llvm/lib/CodeGen/TypePromotion.cpp
+++ b/llvm/lib/CodeGen/TypePromotion.cpp
@@ -175,7 +175,7 @@ class TypePromotionImpl {
bool isSupportedValue(Value *V);
// Is V an instruction thats result can trivially promoted, or has safe
// wrapping.
- bool isLegalToPromote(Value *V);
+ bool isLegalToPromote(Value *V, bool UseSExt = false);
bool TryToPromote(Value *V, unsigned PromotedWidth, const LoopInfo &LI,
bool UseSExt = false);
@@ -415,10 +415,13 @@ bool TypePromotionImpl::shouldPromote(Value *V) {
/// Return whether we can safely mutate V's type to ExtTy without having to be
/// concerned with zero extending or truncation.
-static bool isPromotedResultSafe(Instruction *I) {
+static bool isPromotedResultSafe(Instruction *I, bool UseSExt) {
if (GenerateSignBits(I))
return false;
+ if (UseSExt && I->getOpcode() == Instruction::LShr)
+ return false;
+
if (!isa<OverflowingBinaryOperator>(I))
return true;
@@ -526,7 +529,8 @@ void IRPromoter::PromoteTree() {
else
NewConst = Const->getValue().zext(PromotedWidth);
} else
- NewConst = Const->getValue().zext(PromotedWidth);
+ NewConst = UseSExt ? Const->getValue().sext(PromotedWidth) :
+ Const->getValue().zext(PromotedWidth);
I->setOperand(i, ConstantInt::get(Const->getContext(), NewConst));
} else if (isa<UndefValue>(Op))
@@ -536,7 +540,8 @@ void IRPromoter::PromoteTree() {
// For switch, also mutate case values, which are not operands.
if (auto *SI = dyn_cast<SwitchInst>(I)) {
for (auto Case : SI->cases()) {
- APInt NewConst = Case.getCaseValue()->getValue().zext(PromotedWidth);
+ APInt NewConst = UseSExt ? Case.getCaseValue()->getValue().sext(PromotedWidth) :
+ Case.getCaseValue()->getValue().zext(PromotedWidth);
Case.setValue(ConstantInt::get(SI->getContext(), NewConst));
}
}
@@ -818,7 +823,7 @@ bool TypePromotionImpl::isSupportedValue(Value *V) {
/// Check that the type of V would be promoted and that the original type is
/// smaller than the targeted promoted type. Check that we're not trying to
/// promote something larger than our base 'TypeSize' type.
-bool TypePromotionImpl::isLegalToPromote(Value *V) {
+bool TypePromotionImpl::isLegalToPromote(Value *V, bool UseSExt) {
auto *I = dyn_cast<Instruction>(V);
if (!I)
return true;
@@ -826,7 +831,7 @@ bool TypePromotionImpl::isLegalToPromote(Value *V) {
if (SafeToPromote.count(I))
return true;
- if (isPromotedResultSafe(I) || isSafeWrap(I)) {
+ if (isPromotedResultSafe(I, UseSExt) || (!UseSExt && isSafeWrap(I))) {
SafeToPromote.insert(I);
return true;
}
@@ -840,7 +845,7 @@ bool TypePromotionImpl::TryToPromote(Value *V, unsigned PromotedWidth,
SafeToPromote.clear();
SafeWrap.clear();
- if (!isSupportedValue(V) || !shouldPromote(V) || !isLegalToPromote(V))
+ if (!isSupportedValue(V) || !shouldPromote(V) || !isLegalToPromote(V, UseSExt))
return false;
LLVM_DEBUG(dbgs() << "IR Promotion: TryToPromote: " << *V << ", from "
@@ -864,7 +869,7 @@ bool TypePromotionImpl::TryToPromote(Value *V, unsigned PromotedWidth,
if (isa<GetElementPtrInst>(V))
return false;
- if (!isSupportedValue(V) || (shouldPromote(V) && !isLegalToPromote(V))) {
+ if (!isSupportedValue(V) || (shouldPromote(V) && !isLegalToPromote(V, UseSExt))) {
LLVM_DEBUG(dbgs() << "IR Promotion: Can't handle: " << *V << "\n");
return false;
}
>From fad75f6abe071e8a43c555de22957af6e13ed026 Mon Sep 17 00:00:00 2001
From: Ananth Jasty <ananth at openrv64.org>
Date: Thu, 24 Sep 2026 19:32:52 +0000
Subject: [PATCH 5/9] Added LShr/UDiv/URem escapes to signed promotion.
---
llvm/lib/CodeGen/TypePromotion.cpp | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/CodeGen/TypePromotion.cpp b/llvm/lib/CodeGen/TypePromotion.cpp
index 243cc00c49c7d..ce75ff9d92942 100644
--- a/llvm/lib/CodeGen/TypePromotion.cpp
+++ b/llvm/lib/CodeGen/TypePromotion.cpp
@@ -419,13 +419,15 @@ static bool isPromotedResultSafe(Instruction *I, bool UseSExt) {
if (GenerateSignBits(I))
return false;
- if (UseSExt && I->getOpcode() == Instruction::LShr)
+ if (UseSExt && (I->getOpcode() == Instruction::LShr ||
+ I->getOpcode() == Instruction::UDiv ||
+ I->getOpcode() == Instruction::URem))
return false;
if (!isa<OverflowingBinaryOperator>(I))
return true;
- return I->hasNoUnsignedWrap();
+ return UseSExt ? I->hasNoSignedWrap() : I->hasNoUnsignedWrap();
}
void IRPromoter::ReplaceAllUsersOfWith(Value *From, Value *To) {
>From 69a49dfbae8d4c0bc9da14f4e2dd102a1b31b56d Mon Sep 17 00:00:00 2001
From: Ananth Jasty <ananth at openrv64.org>
Date: Thu, 24 Sep 2026 20:33:46 +0000
Subject: [PATCH 6/9] [CodeGen][RISCV][AArch64] Updated tests to expect results
from signed TypePromotion.
Assisted-by: Codex
---
.../AArch64/typepromotion-zext-nneg.ll | 16 ++-
.../CodeGen/RISCV/typepromotion-zext-nneg.ll | 27 ++---
.../TypePromotion/AArch64/phi-zext-nneg.ll | 109 +++++++++---------
3 files changed, 70 insertions(+), 82 deletions(-)
diff --git a/llvm/test/CodeGen/AArch64/typepromotion-zext-nneg.ll b/llvm/test/CodeGen/AArch64/typepromotion-zext-nneg.ll
index 3a2f25d918382..257f1487bdbb2 100644
--- a/llvm/test/CodeGen/AArch64/typepromotion-zext-nneg.ll
+++ b/llvm/test/CodeGen/AArch64/typepromotion-zext-nneg.ll
@@ -3,7 +3,6 @@
; A signed halfword edge index uses negative values as an end sentinel.
; Both the entry and backedge checks guard the nonnegative index use.
-; FIXME: Promote the index PHI with sign extension to avoid redundant masks.
define i32 @edge_sum(ptr %edges, i16 signext %head) {
; CHECK-LABEL: edge_sum:
@@ -11,18 +10,17 @@ define i32 @edge_sum(ptr %edges, i16 signext %head) {
; CHECK-NEXT: // kill: def $w1 killed $w1 def $x1
; CHECK-NEXT: tbnz w1, #31, .LBB0_4
; CHECK-NEXT: // %bb.1: // %body.preheader
-; CHECK-NEXT: mov x8, x0
-; CHECK-NEXT: mov w0, wzr
-; CHECK-NEXT: and x9, x1, #0xffff
+; CHECK-NEXT: sxtw x9, w1
+; CHECK-NEXT: mov w8, wzr
; CHECK-NEXT: .LBB0_2: // %body
; CHECK-NEXT: // =>This Inner Loop Header: Depth=1
-; CHECK-NEXT: add x9, x8, x9, lsl #3
+; CHECK-NEXT: add x9, x0, x9, lsl #3
; CHECK-NEXT: ldrsh w10, [x9, #4]
-; CHECK-NEXT: ldrsh w11, [x9, #2]
-; CHECK-NEXT: add w0, w0, w10
-; CHECK-NEXT: and x9, x11, #0xffff
-; CHECK-NEXT: tbz w11, #31, .LBB0_2
+; CHECK-NEXT: ldrsh x9, [x9, #2]
+; CHECK-NEXT: add w8, w8, w10
+; CHECK-NEXT: tbz x9, #63, .LBB0_2
; CHECK-NEXT: // %bb.3: // %exit
+; CHECK-NEXT: mov w0, w8
; CHECK-NEXT: ret
; CHECK-NEXT: .LBB0_4:
; CHECK-NEXT: mov w0, wzr
diff --git a/llvm/test/CodeGen/RISCV/typepromotion-zext-nneg.ll b/llvm/test/CodeGen/RISCV/typepromotion-zext-nneg.ll
index 58f413b51a977..2c6f822cf7844 100644
--- a/llvm/test/CodeGen/RISCV/typepromotion-zext-nneg.ll
+++ b/llvm/test/CodeGen/RISCV/typepromotion-zext-nneg.ll
@@ -3,28 +3,21 @@
; A signed halfword edge index uses negative values as an end sentinel.
; Both the entry and backedge checks guard the nonnegative index use.
-; FIXME: Promote the index PHI with sign extension to avoid redundant masks.
define i32 @edge_sum(ptr %edges, i16 signext %head) {
; CHECK-LABEL: edge_sum:
; CHECK: # %bb.0: # %entry
-; CHECK-NEXT: bltz a1, .LBB0_4
-; CHECK-NEXT: # %bb.1: # %body.preheader
-; CHECK-NEXT: mv a2, a0
-; CHECK-NEXT: li a0, 0
-; CHECK-NEXT: zext.h a1, a1
-; CHECK-NEXT: .LBB0_2: # %body
+; CHECK-NEXT: li a2, 0
+; CHECK-NEXT: bltz a1, .LBB0_2
+; CHECK-NEXT: .LBB0_1: # %body
; CHECK-NEXT: # =>This Inner Loop Header: Depth=1
-; CHECK-NEXT: sh3add a1, a1, a2
-; CHECK-NEXT: lh a3, 2(a1)
-; CHECK-NEXT: lh a1, 4(a1)
-; CHECK-NEXT: addw a0, a0, a1
-; CHECK-NEXT: zext.h a1, a3
-; CHECK-NEXT: bgez a3, .LBB0_2
-; CHECK-NEXT: # %bb.3: # %exit
-; CHECK-NEXT: ret
-; CHECK-NEXT: .LBB0_4:
-; CHECK-NEXT: li a0, 0
+; CHECK-NEXT: sh3add a3, a1, a0
+; CHECK-NEXT: lh a1, 2(a3)
+; CHECK-NEXT: lh a3, 4(a3)
+; CHECK-NEXT: addw a2, a2, a3
+; CHECK-NEXT: bgez a1, .LBB0_1
+; CHECK-NEXT: .LBB0_2: # %exit
+; CHECK-NEXT: mv a0, a2
; CHECK-NEXT: ret
entry:
%ok = icmp sge i16 %head, 0
diff --git a/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg.ll b/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg.ll
index 5efc97922f5ee..b19e17df37ee3 100644
--- a/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg.ll
+++ b/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg.ll
@@ -6,7 +6,7 @@ define i32 @edge_sum(ptr %edges, i16 signext %head) {
; CHECK-LABEL: define i32 @edge_sum(
; CHECK-SAME: ptr [[EDGES:%.*]], i16 signext [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[TMP0]] to i16
; CHECK-NEXT: [[OK:%.*]] = icmp sge i16 [[TMP1]], 0
; CHECK-NEXT: br i1 [[OK]], label %[[BODY:.*]], label %[[EXIT:.*]]
@@ -20,7 +20,7 @@ define i32 @edge_sum(ptr %edges, i16 signext %head) {
; CHECK-NEXT: [[VALUE_WIDE:%.*]] = sext i16 [[VALUE]] to i32
; CHECK-NEXT: [[SUM_NEXT]] = add i32 [[SUM]], [[VALUE_WIDE]]
; CHECK-NEXT: [[NEXT:%.*]] = load i16, ptr [[NEXT_PTR]], align 2
-; CHECK-NEXT: [[TMP2]] = zext i16 [[NEXT]] to i64
+; CHECK-NEXT: [[TMP2]] = sext i16 [[NEXT]] to i64
; CHECK-NEXT: [[TMP3:%.*]] = trunc i64 [[TMP2]] to i16
; CHECK-NEXT: [[CONTINUE:%.*]] = icmp sge i16 [[TMP3]], 0
; CHECK-NEXT: br i1 [[CONTINUE]], label %[[BODY]], label %[[EXIT]]
@@ -107,10 +107,11 @@ define i64 @mixed_extensions(i16 %head) {
; CHECK-LABEL: define i64 @mixed_extensions(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
-; CHECK-NEXT: [[UNSIGNED:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[TMP0]] to i16
-; CHECK-NEXT: [[OK:%.*]] = icmp sge i16 [[TMP1]], 0
+; CHECK-NEXT: [[UNSIGNED:%.*]] = zext i16 [[TMP1]] to i64
+; CHECK-NEXT: [[TMP2:%.*]] = trunc i64 [[TMP0]] to i16
+; CHECK-NEXT: [[OK:%.*]] = icmp sge i16 [[TMP2]], 0
; CHECK-NEXT: br i1 [[OK]], label %[[LOOP:.*]], label %[[EXIT:.*]]
; CHECK: [[LOOP]]:
; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[LOOP]] ]
@@ -140,7 +141,7 @@ define i1 @negative_constant(i16 %head) {
; CHECK-LABEL: define i1 @negative_constant(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
@@ -151,7 +152,7 @@ define i1 @negative_constant(i16 %head) {
; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
; CHECK: [[EXIT]]:
-; CHECK-NEXT: [[MATCH:%.*]] = icmp eq i64 [[TMP0]], 65535
+; CHECK-NEXT: [[MATCH:%.*]] = icmp eq i64 [[TMP0]], -1
; CHECK-NEXT: ret i1 [[MATCH]]
;
entry:
@@ -177,10 +178,10 @@ define i64 @negative_phi_constant(i16 %head) {
; CHECK-LABEL: define i64 @negative_phi_constant(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 65535, %[[BODY:.*]] ]
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ -1, %[[BODY:.*]] ]
; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IDX]], %[[BODY]] ]
; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
@@ -212,7 +213,7 @@ define i32 @negative_switch_case(i16 %head) {
; CHECK-LABEL: define i32 @negative_switch_case(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
@@ -224,7 +225,7 @@ define i32 @negative_switch_case(i16 %head) {
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
; CHECK: [[EXIT]]:
; CHECK-NEXT: switch i64 [[TMP0]], label %[[OTHER:.*]] [
-; CHECK-NEXT: i64 65535, label %[[MINUS_ONE:.*]]
+; CHECK-NEXT: i64 -1, label %[[MINUS_ONE:.*]]
; CHECK-NEXT: ]
; CHECK: [[MINUS_ONE]]:
; CHECK-NEXT: ret i32 1
@@ -259,16 +260,15 @@ define i64 @phi_lshr(i16 %head) {
; CHECK-LABEL: define i64 @phi_lshr(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
-; CHECK-NEXT: [[INITIAL:%.*]] = lshr i64 [[TMP0]], 1
+; CHECK-NEXT: [[INITIAL:%.*]] = lshr i16 [[HEAD]], 1
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
-; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
; CHECK: [[BODY]]:
+; CHECK-NEXT: [[IDX:%.*]] = zext nneg i16 [[TMP1]] to i64
; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
@@ -302,16 +302,15 @@ define i64 @phi_udiv(i16 %head) {
; CHECK-LABEL: define i64 @phi_udiv(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
-; CHECK-NEXT: [[INITIAL:%.*]] = udiv i64 [[TMP0]], 3
+; CHECK-NEXT: [[INITIAL:%.*]] = udiv i16 [[HEAD]], 3
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
-; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
; CHECK: [[BODY]]:
+; CHECK-NEXT: [[IDX:%.*]] = zext nneg i16 [[TMP1]] to i64
; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
@@ -345,16 +344,15 @@ define i64 @phi_urem(i16 %head) {
; CHECK-LABEL: define i64 @phi_urem(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
-; CHECK-NEXT: [[INITIAL:%.*]] = urem i64 [[TMP0]], 7
+; CHECK-NEXT: [[INITIAL:%.*]] = urem i16 [[HEAD]], 7
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
-; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
; CHECK: [[BODY]]:
+; CHECK-NEXT: [[IDX:%.*]] = zext nneg i16 [[TMP1]] to i64
; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
@@ -388,15 +386,16 @@ define i64 @phi_add_nsw(i16 %head) {
; CHECK-LABEL: define i64 @phi_add_nsw(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[INITIAL:%.*]] = add nsw i16 [[HEAD]], 1
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[INITIAL:%.*]] = add nsw i64 [[TMP0]], 1
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IDX:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[WIDE:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[IDX:%.*]] = trunc i64 [[WIDE]] to i16
; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[IDX]], 0
; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
; CHECK: [[BODY]]:
-; CHECK-NEXT: [[WIDE:%.*]] = zext nneg i16 [[IDX]] to i64
; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[WIDE]]
; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[WIDE]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
@@ -430,16 +429,15 @@ define i64 @phi_add_nuw(i16 %head) {
; CHECK-LABEL: define i64 @phi_add_nuw(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
-; CHECK-NEXT: [[INITIAL:%.*]] = add nuw i64 [[TMP0]], 1
+; CHECK-NEXT: [[INITIAL:%.*]] = add nuw i16 [[HEAD]], 1
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
-; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
; CHECK: [[BODY]]:
+; CHECK-NEXT: [[IDX:%.*]] = zext nneg i16 [[TMP1]] to i64
; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
@@ -473,15 +471,16 @@ define i64 @phi_sub_nsw(i16 %head) {
; CHECK-LABEL: define i64 @phi_sub_nsw(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[INITIAL:%.*]] = sub nsw i16 [[HEAD]], 1
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[INITIAL:%.*]] = sub nsw i64 [[TMP0]], 1
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IDX:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[WIDE:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[IDX:%.*]] = trunc i64 [[WIDE]] to i16
; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[IDX]], 0
; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
; CHECK: [[BODY]]:
-; CHECK-NEXT: [[WIDE:%.*]] = zext nneg i16 [[IDX]] to i64
; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[WIDE]]
; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[WIDE]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
@@ -515,16 +514,15 @@ define i64 @phi_sub_nuw(i16 %head) {
; CHECK-LABEL: define i64 @phi_sub_nuw(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
-; CHECK-NEXT: [[INITIAL:%.*]] = sub nuw i64 [[TMP0]], 1
+; CHECK-NEXT: [[INITIAL:%.*]] = sub nuw i16 [[HEAD]], 1
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
-; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
; CHECK: [[BODY]]:
+; CHECK-NEXT: [[IDX:%.*]] = zext nneg i16 [[TMP1]] to i64
; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
@@ -558,15 +556,16 @@ define i64 @phi_mul_nsw(i16 %head) {
; CHECK-LABEL: define i64 @phi_mul_nsw(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[INITIAL:%.*]] = mul nsw i16 [[HEAD]], 2
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[INITIAL:%.*]] = mul nsw i64 [[TMP0]], 2
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IDX:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[WIDE:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[IDX:%.*]] = trunc i64 [[WIDE]] to i16
; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[IDX]], 0
; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
; CHECK: [[BODY]]:
-; CHECK-NEXT: [[WIDE:%.*]] = zext nneg i16 [[IDX]] to i64
; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[WIDE]]
; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[WIDE]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
@@ -600,16 +599,15 @@ define i64 @phi_mul_nuw(i16 %head) {
; CHECK-LABEL: define i64 @phi_mul_nuw(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
-; CHECK-NEXT: [[INITIAL:%.*]] = mul nuw i64 [[TMP0]], 2
+; CHECK-NEXT: [[INITIAL:%.*]] = mul nuw i16 [[HEAD]], 2
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
-; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
; CHECK: [[BODY]]:
+; CHECK-NEXT: [[IDX:%.*]] = zext nneg i16 [[TMP1]] to i64
; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
@@ -643,15 +641,16 @@ define i64 @phi_shl_nsw(i16 %head) {
; CHECK-LABEL: define i64 @phi_shl_nsw(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[INITIAL:%.*]] = shl nsw i16 [[HEAD]], 1
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[INITIAL:%.*]] = shl nsw i64 [[TMP0]], 1
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IDX:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[WIDE:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[IDX:%.*]] = trunc i64 [[WIDE]] to i16
; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[IDX]], 0
; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
; CHECK: [[BODY]]:
-; CHECK-NEXT: [[WIDE:%.*]] = zext nneg i16 [[IDX]] to i64
; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[WIDE]]
; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[WIDE]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
@@ -685,16 +684,15 @@ define i64 @phi_shl_nuw(i16 %head) {
; CHECK-LABEL: define i64 @phi_shl_nuw(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
-; CHECK-NEXT: [[INITIAL:%.*]] = shl nuw i64 [[TMP0]], 1
+; CHECK-NEXT: [[INITIAL:%.*]] = shl nuw i16 [[HEAD]], 1
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
-; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
; CHECK: [[BODY]]:
+; CHECK-NEXT: [[IDX:%.*]] = zext nneg i16 [[TMP1]] to i64
; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
@@ -729,19 +727,18 @@ define i1 @unsigned_wrap(i16 %head) {
; CHECK-LABEL: define i1 @unsigned_wrap(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
-; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[TMP1:%.*]] = phi i16 [ [[HEAD]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
; CHECK: [[BODY]]:
+; CHECK-NEXT: [[IDX:%.*]] = zext nneg i16 [[TMP1]] to i64
; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
; CHECK: [[EXIT]]:
-; CHECK-NEXT: [[OFFSET:%.*]] = add i64 [[TMP0]], -2
-; CHECK-NEXT: [[IN_RANGE:%.*]] = icmp ule i64 [[OFFSET]], 65533
+; CHECK-NEXT: [[OFFSET:%.*]] = add i16 [[HEAD]], -2
+; CHECK-NEXT: [[IN_RANGE:%.*]] = icmp ule i16 [[OFFSET]], -3
; CHECK-NEXT: ret i1 [[IN_RANGE]]
;
entry:
>From 5f00e163dde16358e60522ca9314364a41ff163b Mon Sep 17 00:00:00 2001
From: Ananth Jasty <ananth at openrv64.org>
Date: Thu, 24 Sep 2026 20:46:05 +0000
Subject: [PATCH 7/9] [CodeGen] Reject ordinary intermediate zext in signed
TypePromotion
An ordinary zext feeding a loop PHI cannot be promoted by sign extending
its source: zext i8 -1 to i16 produces 255, whereas sext produces -1.
The signed promotion path previously admitted this instruction and left
an invalid same-width zext that failed verification.
Reject intermediate zext instructions without nneg in signed mode.
Keep nneg extensions promotable and preserve ordinary zext sinks.
Add IR coverage for these cases, promotion widths, select and bitwise
operations, sources, and mixed extension sinks.
Assisted-by: Codex
---
llvm/lib/CodeGen/TypePromotion.cpp | 5 +
.../AArch64/phi-zext-nneg-sources-sinks.ll | 536 ++++++++++++++++++
2 files changed, 541 insertions(+)
create mode 100644 llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg-sources-sinks.ll
diff --git a/llvm/lib/CodeGen/TypePromotion.cpp b/llvm/lib/CodeGen/TypePromotion.cpp
index ce75ff9d92942..5cb0666d10c5f 100644
--- a/llvm/lib/CodeGen/TypePromotion.cpp
+++ b/llvm/lib/CodeGen/TypePromotion.cpp
@@ -424,6 +424,11 @@ static bool isPromotedResultSafe(Instruction *I, bool UseSExt) {
I->getOpcode() == Instruction::URem))
return false;
+ if (UseSExt)
+ if (auto *ZExt = dyn_cast<ZExtInst>(I))
+ if (!ZExt->hasNonNeg())
+ return false;
+
if (!isa<OverflowingBinaryOperator>(I))
return true;
diff --git a/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg-sources-sinks.ll b/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg-sources-sinks.ll
new file mode 100644
index 0000000000000..b06d5d296bbb9
--- /dev/null
+++ b/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg-sources-sinks.ll
@@ -0,0 +1,536 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -mtriple=aarch64 -passes=typepromotion,verify -S < %s | FileCheck %s
+
+declare zeroext i16 @read_index()
+
+; Promote an i8 PHI to a width smaller than the scalar register width.
+define i32 @phi_i8_to_i32(i8 %head) {
+; CHECK-LABEL: define i32 @phi_i8_to_i32(
+; CHECK-SAME: i8 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = sext i8 [[HEAD]] to i32
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i32 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i32 [[IDX]] to i8
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i8 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[SUM_NEXT]] = add i32 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i32 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i32 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i32 [[RESULT]]
+;
+entry:
+ br label %loop
+
+loop:
+ %idx = phi i8 [ %head, %entry ], [ 0, %body ]
+ %sum = phi i32 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i8 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i8 %idx to i32
+ %sum.next = add i32 %sum, %wide
+ %again = icmp ne i32 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i32 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i32 %result
+}
+
+; The PHI root also supports promotion from i32 to i64.
+define i64 @phi_i32_to_i64(i32 %head) {
+; CHECK-LABEL: define i64 @phi_i32_to_i64(
+; CHECK-SAME: i32 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = sext i32 [[HEAD]] to i64
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i32
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i32 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ br label %loop
+
+loop:
+ %idx = phi i32 [ %head, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i32 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i32 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; Widen select values, including a negative sentinel, but not its condition.
+define i64 @phi_select(i1 %choose, i16 %head) {
+; CHECK-LABEL: define i64 @phi_select(
+; CHECK-SAME: i1 [[CHOOSE:%.*]], i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[INITIAL:%.*]] = select i1 [[CHOOSE]], i64 [[TMP0]], i64 -1
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ %initial = select i1 %choose, i16 %head, i16 -1
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; Sign extension must commute with bitwise operations and their constants.
+define i64 @phi_xor(i16 %head) {
+; CHECK-LABEL: define i64 @phi_xor(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[INITIAL:%.*]] = xor i64 [[TMP0]], -32768
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ %initial = xor i16 %head, -32768
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; A truncation producing the PHI width is a source; preserve the truncation
+; before sign extending its result. The original i64 value need not fit i16.
+define i64 @phi_trunc_source(i64 %input) {
+; CHECK-LABEL: define i64 @phi_trunc_source(
+; CHECK-SAME: i64 [[INPUT:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[HEAD:%.*]] = trunc i64 [[INPUT]] to i16
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ %head = trunc i64 %input to i16
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %head, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; A zeroext return attribute describes the ABI, not the signedness of the
+; i16 payload. A returned negative sentinel must still exit the loop.
+define i64 @phi_call_source() {
+; CHECK-LABEL: define i64 @phi_call_source() {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[HEAD:%.*]] = call zeroext i16 @read_index()
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ %head = call zeroext i16 @read_index()
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %head, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; An ordinary extension to i32 must keep its unsigned meaning when the
+; connected PHI is promoted using sign extension to i64.
+define i32 @unsigned_i32_sink(i16 %head) {
+; CHECK-LABEL: define i32 @unsigned_i32_sink(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[TMP0]] to i16
+; CHECK-NEXT: [[UNSIGNED:%.*]] = zext i16 [[TMP1]] to i32
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[TMP2:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP2]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i32 [[UNSIGNED]]
+;
+entry:
+ %unsigned = zext i16 %head to i32
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %head, %entry ], [ 0, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ ret i32 %unsigned
+}
+
+; Even a matching nneg extension needs truncation when its destination
+; width is smaller than the promoted PHI.
+define i64 @narrower_nneg_sink(i16 %head) {
+; CHECK-LABEL: define i64 @narrower_nneg_sink(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[TMP2:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NARROW:%.*]] = zext nneg i16 [[TMP2]] to i32
+; CHECK-NEXT: [[NARROW_WIDE:%.*]] = zext i32 [[NARROW]] to i64
+; CHECK-NEXT: [[BOTH:%.*]] = add i64 [[IDX]], [[NARROW_WIDE]]
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[BOTH]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %head, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %narrow = zext nneg i16 %idx to i32
+ %narrow.wide = zext i32 %narrow to i64
+ %both = add i64 %wide, %narrow.wide
+ %sum.next = add i64 %sum, %both
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; Promote to i32 while retaining a matching extension to i64. Removing
+; the latter extension entirely would leave a value of the wrong width.
+define i64 @wider_nneg_sink(i16 %head) {
+; CHECK-LABEL: define i64 @wider_nneg_sink(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i32
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i32 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i32 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[OTHER:%.*]] = zext nneg i32 [[IDX]] to i64
+; CHECK-NEXT: [[FIRST:%.*]] = zext i32 [[IDX]] to i64
+; CHECK-NEXT: [[BOTH:%.*]] = add i64 [[FIRST]], [[OTHER]]
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[BOTH]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i32 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SUM]], %[[LOOP]] ], [ [[SUM_NEXT]], %[[BODY]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %head, %entry ], [ 0, %body ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i32
+ %other = zext nneg i16 %idx to i64
+ %first = zext i32 %wide to i64
+ %both = add i64 %first, %other
+ %sum.next = add i64 %sum, %both
+ %again = icmp ne i32 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %sum, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; Stores and returns retain their original width after source promotion,
+; including for a negative value which never executes the nneg extension.
+define i16 @store_and_return_sinks(i16 %head, ptr %out) {
+; CHECK-LABEL: define i16 @store_and_return_sinks(
+; CHECK-SAME: i16 [[HEAD:%.*]], ptr [[OUT:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[TMP0]] to i16
+; CHECK-NEXT: store i16 [[TMP1]], ptr [[OUT]], align 2
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[TMP2:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP2]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[TMP3:%.*]] = trunc i64 [[TMP0]] to i16
+; CHECK-NEXT: ret i16 [[TMP3]]
+;
+entry:
+ store i16 %head, ptr %out, align 2
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %head, %entry ], [ 0, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ ret i16 %head
+}
+
+; An existing sext user currently prevents promotion. Its signed result
+; must remain intact, particularly when the input is negative.
+define i64 @existing_sext_sink(i16 %head) {
+; CHECK-LABEL: define i64 @existing_sext_sink(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[SIGNED:%.*]] = sext i16 [[HEAD]] to i64
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i16 [ [[HEAD]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[IDX]], 0
+; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[WIDE:%.*]] = zext nneg i16 [[IDX]] to i64
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[WIDE]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i64 [[SIGNED]]
+;
+entry:
+ %signed = sext i16 %head to i64
+ br label %loop
+
+loop:
+ %idx = phi i16 [ %head, %entry ], [ 0, %body ]
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext nneg i16 %idx to i64
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ ret i64 %signed
+}
+
+; An ordinary intermediate zext must retain its unsigned semantics. For
+; example, an i8 input of -1 produces 255, not -1. Reject signed promotion
+; through this extension rather than creating an invalid same-width cast.
+define i64 @intermediate_zext(i8 %head) {
+; CHECK-LABEL: define i64 @intermediate_zext(
+; CHECK-SAME: i8 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[INITIAL:%.*]] = zext i8 [[HEAD]] to i16
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i16 [ [[INITIAL]], %[[ENTRY]] ], [ 0, %[[LOOP]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[WIDE:%.*]] = zext nneg i16 [[IDX]] to i64
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[WIDE]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[WIDE]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i64 [[SUM_NEXT]]
+;
+entry:
+ %initial = zext i8 %head to i16
+ br label %loop
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %loop ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %loop ]
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+exit:
+ ret i64 %sum.next
+}
+
+; With nneg on the intermediate extension, sign and zero extension agree.
+; Signed promotion can widen the PHI and remove both extensions.
+define i64 @intermediate_zext_nneg(i8 %head) {
+; CHECK-LABEL: define i64 @intermediate_zext_nneg(
+; CHECK-SAME: i8 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = sext i8 [[HEAD]] to i64
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[TMP0]], %[[ENTRY]] ], [ 0, %[[LOOP]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i64 [[SUM_NEXT]]
+;
+entry:
+ %initial = zext nneg i8 %head to i16
+ br label %loop
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %loop ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %loop ]
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+exit:
+ ret i64 %sum.next
+}
>From 24b4d86a7c5dfba3c9ea4047e2763a7041bb5067 Mon Sep 17 00:00:00 2001
From: Ananth Jasty <ananth at openrv64.org>
Date: Thu, 24 Sep 2026 21:11:38 +0000
Subject: [PATCH 8/9] [CodeGen] Added tests for signed TypePromotion.
Generated test cases found a bug, missed coverage of Truncs+zext
handling, added trunc+sext for UseSExt.
Assisted-by: Codex
---
llvm/lib/CodeGen/TypePromotion.cpp | 22 ++++--
.../AArch64/phi-zext-nneg-internal-trunc.ll | 73 +++++++++++++++++++
2 files changed, 87 insertions(+), 8 deletions(-)
create mode 100644 llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg-internal-trunc.ll
diff --git a/llvm/lib/CodeGen/TypePromotion.cpp b/llvm/lib/CodeGen/TypePromotion.cpp
index 5cb0666d10c5f..acfc3003d7cfa 100644
--- a/llvm/lib/CodeGen/TypePromotion.cpp
+++ b/llvm/lib/CodeGen/TypePromotion.cpp
@@ -693,16 +693,22 @@ void IRPromoter::ConvertTruncs() {
IntegerType *DestTy = cast<IntegerType>(TruncTysMap[Trunc][0]);
unsigned NumBits = DestTy->getScalarSizeInBits();
- ConstantInt *Mask =
- ConstantInt::get(SrcTy, APInt::getMaxValue(NumBits).getZExtValue());
- Value *Masked = Builder.CreateAnd(Trunc->getOperand(0), Mask);
- if (SrcTy->getBitWidth() > ExtTy->getBitWidth())
- Masked = Builder.CreateTrunc(Masked, ExtTy);
-
- if (auto *I = dyn_cast<Instruction>(Masked))
+ // Signed promotion path truncates then sign extends
+ Value *Final;
+ if (UseSExt) {
+ Value *Narrow = Builder.CreateTrunc(Trunc->getOperand(0), DestTy);
+ Final = Builder.CreateSExt(Narrow, ExtTy);
+ } else {
+ ConstantInt *Mask =
+ ConstantInt::get(SrcTy, APInt::getMaxValue(NumBits).getZExtValue());
+ Final = Builder.CreateAnd(Trunc->getOperand(0), Mask);
+ if (SrcTy->getBitWidth() > ExtTy->getBitWidth())
+ Final = Builder.CreateTrunc(Final, ExtTy);
+ }
+ if (auto *I = dyn_cast<Instruction>(Final))
NewInsts.insert(I);
- ReplaceAllUsersOfWith(Trunc, Masked);
+ ReplaceAllUsersOfWith(Trunc, Final);
}
}
diff --git a/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg-internal-trunc.ll b/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg-internal-trunc.ll
new file mode 100644
index 0000000000000..7b20fc07215b6
--- /dev/null
+++ b/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg-internal-trunc.ll
@@ -0,0 +1,73 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -mtriple=aarch64 -passes=typepromotion,verify -S < %s | FileCheck %s
+
+; Preserve the signed interpretation of an internal truncation. For head = -1,
+; truncation to i8 followed by XOR with -128 produces 127. Using an unsigned
+; mask in the promoted tree instead produces -129.
+define i64 @internal_trunc(i16 %head) {
+; CHECK-LABEL: define i64 @internal_trunc(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[TMP0]] to i8
+; CHECK-NEXT: [[TMP2:%.*]] = sext i8 [[TMP1]] to i64
+; CHECK-NEXT: [[FLIPPED:%.*]] = xor i64 [[TMP2]], -128
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[FLIPPED]], %[[ENTRY]] ], [ 0, %[[LOOP]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i64 [[SUM_NEXT]]
+;
+entry:
+ %narrow = trunc i16 %head to i8
+ %flipped = xor i8 %narrow, -128
+ %initial = zext nneg i8 %flipped to i16
+ br label %loop
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %loop ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %loop ]
+ %wide = zext nneg i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+exit:
+ ret i64 %sum.next
+}
+
+; Unsigned promotion continues to use a mask for the internal truncation.
+define i64 @internal_trunc_unsigned(i16 %head) {
+; CHECK-LABEL: define i64 @internal_trunc_unsigned(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = and i64 [[TMP0]], 255
+; CHECK-NEXT: [[FLIPPED:%.*]] = xor i64 [[TMP1]], 128
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i64 [ [[FLIPPED]], %[[ENTRY]] ], [ 0, %[[LOOP]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[IDX]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i64 [[SUM_NEXT]]
+;
+entry:
+ %narrow = trunc i16 %head to i8
+ %flipped = xor i8 %narrow, -128
+ %initial = zext i8 %flipped to i16
+ br label %loop
+loop:
+ %idx = phi i16 [ %initial, %entry ], [ 0, %loop ]
+ %sum = phi i64 [ 0, %entry ], [ %sum.next, %loop ]
+ %wide = zext i16 %idx to i64
+ %sum.next = add i64 %sum, %wide
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+exit:
+ ret i64 %sum.next
+}
>From 921aafee8aa2d216592e1aba5d3b4c1cf77320fb Mon Sep 17 00:00:00 2001
From: Ananth Jasty <ananth at openrv64.org>
Date: Thu, 24 Sep 2026 21:15:18 +0000
Subject: [PATCH 9/9] [CodeGen] clang-format fixed formatting issues.
---
llvm/lib/CodeGen/TypePromotion.cpp | 26 ++++++++++++++++----------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/llvm/lib/CodeGen/TypePromotion.cpp b/llvm/lib/CodeGen/TypePromotion.cpp
index acfc3003d7cfa..31a4dcfd419bc 100644
--- a/llvm/lib/CodeGen/TypePromotion.cpp
+++ b/llvm/lib/CodeGen/TypePromotion.cpp
@@ -131,7 +131,8 @@ class IRPromoter {
SmallPtrSetImpl<Instruction *> &instsToRemove,
bool useSExt = false)
: Ctx(C), PromotedWidth(Width), Visited(visited), Sources(sources),
- Sinks(sinks), SafeWrap(wrap), InstsToRemove(instsToRemove), UseSExt(useSExt) {
+ Sinks(sinks), SafeWrap(wrap), InstsToRemove(instsToRemove),
+ UseSExt(useSExt) {
ExtTy = IntegerType::get(Ctx, PromotedWidth);
}
@@ -470,7 +471,8 @@ void IRPromoter::ExtendSources() {
if (auto *I = dyn_cast<Instruction>(V))
Builder.SetCurrentDebugLocation(I->getDebugLoc());
- Value *Ext = UseSExt ? Builder.CreateSExt(V, ExtTy) : Builder.CreateZExt(V, ExtTy);
+ Value *Ext =
+ UseSExt ? Builder.CreateSExt(V, ExtTy) : Builder.CreateZExt(V, ExtTy);
if (auto *I = dyn_cast<Instruction>(Ext)) {
if (isa<Argument>(V))
I->moveBefore(InsertPt);
@@ -536,8 +538,8 @@ void IRPromoter::PromoteTree() {
else
NewConst = Const->getValue().zext(PromotedWidth);
} else
- NewConst = UseSExt ? Const->getValue().sext(PromotedWidth) :
- Const->getValue().zext(PromotedWidth);
+ NewConst = UseSExt ? Const->getValue().sext(PromotedWidth)
+ : Const->getValue().zext(PromotedWidth);
I->setOperand(i, ConstantInt::get(Const->getContext(), NewConst));
} else if (isa<UndefValue>(Op))
@@ -547,8 +549,9 @@ void IRPromoter::PromoteTree() {
// For switch, also mutate case values, which are not operands.
if (auto *SI = dyn_cast<SwitchInst>(I)) {
for (auto Case : SI->cases()) {
- APInt NewConst = UseSExt ? Case.getCaseValue()->getValue().sext(PromotedWidth) :
- Case.getCaseValue()->getValue().zext(PromotedWidth);
+ APInt NewConst =
+ UseSExt ? Case.getCaseValue()->getValue().sext(PromotedWidth)
+ : Case.getCaseValue()->getValue().zext(PromotedWidth);
Case.setValue(ConstantInt::get(SI->getContext(), NewConst));
}
}
@@ -626,7 +629,8 @@ void IRPromoter::TruncateSinks() {
// for the zext will be promoted to the same width as the ext's return type
// rendering that ext unnecessary. This zext gets removed before the end
// of the pass.
- if (CheckSignedMatch(I) && (I->getType()->getScalarSizeInBits() >= PromotedWidth))
+ if (CheckSignedMatch(I) &&
+ (I->getType()->getScalarSizeInBits() >= PromotedWidth))
continue;
// Now handle the others.
@@ -852,13 +856,14 @@ bool TypePromotionImpl::isLegalToPromote(Value *V, bool UseSExt) {
}
bool TypePromotionImpl::TryToPromote(Value *V, unsigned PromotedWidth,
- const LoopInfo &LI, bool UseSExt) {
+ const LoopInfo &LI, bool UseSExt) {
Type *OrigTy = V->getType();
TypeSize = OrigTy->getPrimitiveSizeInBits().getFixedValue();
SafeToPromote.clear();
SafeWrap.clear();
- if (!isSupportedValue(V) || !shouldPromote(V) || !isLegalToPromote(V, UseSExt))
+ if (!isSupportedValue(V) || !shouldPromote(V) ||
+ !isLegalToPromote(V, UseSExt))
return false;
LLVM_DEBUG(dbgs() << "IR Promotion: TryToPromote: " << *V << ", from "
@@ -882,7 +887,8 @@ bool TypePromotionImpl::TryToPromote(Value *V, unsigned PromotedWidth,
if (isa<GetElementPtrInst>(V))
return false;
- if (!isSupportedValue(V) || (shouldPromote(V) && !isLegalToPromote(V, UseSExt))) {
+ if (!isSupportedValue(V) ||
+ (shouldPromote(V) && !isLegalToPromote(V, UseSExt))) {
LLVM_DEBUG(dbgs() << "IR Promotion: Can't handle: " << *V << "\n");
return false;
}
More information about the llvm-commits
mailing list