[llvm] [CodeGen][RISCV][AArch64] Added signed extension support to TypePromotion (PR #226304)
Ananth Jasty via llvm-commits
llvm-commits at lists.llvm.org
Sun Sep 27 22:29:49 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 01/15] [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 02/15] [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 03/15] [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 04/15] [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 05/15] 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 06/15] [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 07/15] [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 08/15] [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 09/15] [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;
}
>From 8ee1b90457d1bf5ac5c7a790b39e29a4573da6f9 Mon Sep 17 00:00:00 2001
From: Ananth Jasty <ananth at openrv64.org>
Date: Thu, 24 Sep 2026 23:15:39 +0000
Subject: [PATCH 10/15] [CodeGen][RISCV][AArch64] Unblocked signed Sinked
values in TypePromotion.
This eliminates extra sexts in testing, have not seen an effect in code
yet.
Assisted-by: Codex
---
llvm/lib/CodeGen/TypePromotion.cpp | 12 +-
.../AArch64/phi-zext-nneg-sources-sinks.ll | 223 +++++++++++++++++-
2 files changed, 225 insertions(+), 10 deletions(-)
diff --git a/llvm/lib/CodeGen/TypePromotion.cpp b/llvm/lib/CodeGen/TypePromotion.cpp
index 31a4dcfd419bc..947a10c9f7145 100644
--- a/llvm/lib/CodeGen/TypePromotion.cpp
+++ b/llvm/lib/CodeGen/TypePromotion.cpp
@@ -249,7 +249,8 @@ bool TypePromotionImpl::isSource(Value *V) {
else if (isa<LoadInst>(V))
return true;
else if (auto *Call = dyn_cast<CallInst>(V))
- return Call->hasRetAttr(Attribute::AttrKind::ZExt);
+ return (Call->hasRetAttr(Attribute::AttrKind::ZExt) ||
+ Call->hasRetAttr(Attribute::AttrKind::SExt));
else if (auto *Trunc = dyn_cast<TruncInst>(V))
return EqualTypeSize(Trunc);
return false;
@@ -787,9 +788,8 @@ bool TypePromotionImpl::isSupportedType(Value *V) {
}
/// We accept most instructions, as well as Arguments and ConstantInsts. We
-/// Disallow casts other than zext and truncs and only allow calls if their
-/// return value is zeroext. We don't allow opcodes that can introduce sign
-/// bits.
+/// Disallow casts other than zext/sext and truncs and only allow calls if their
+/// return value is zext/sext.
bool TypePromotionImpl::isSupportedValue(Value *V) {
if (auto *I = dyn_cast<Instruction>(V)) {
switch (I->getOpcode()) {
@@ -811,6 +811,7 @@ bool TypePromotionImpl::isSupportedValue(Value *V) {
case Instruction::BitCast:
return I->getOperand(0)->getType() == I->getType();
case Instruction::ZExt:
+ case Instruction::SExt:
return isSupportedType(I->getOperand(0));
case Instruction::ICmp:
// Now that we allow small types than TypeSize, only allow icmp of
@@ -826,7 +827,8 @@ bool TypePromotionImpl::isSupportedValue(Value *V) {
// can still be sinks.
auto *Call = cast<CallInst>(I);
return isSupportedType(Call) &&
- Call->hasRetAttr(Attribute::AttrKind::ZExt);
+ (Call->hasRetAttr(Attribute::AttrKind::ZExt) ||
+ Call->hasRetAttr(Attribute::AttrKind::SExt));
}
}
} else if (isa<Constant>(V) && !isa<ConstantExpr>(V)) {
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
index b06d5d296bbb9..8d7752d96db07 100644
--- a/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg-sources-sinks.ll
+++ b/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg-sources-sinks.ll
@@ -2,6 +2,7 @@
; RUN: opt -mtriple=aarch64 -passes=typepromotion,verify -S < %s | FileCheck %s
declare zeroext i16 @read_index()
+declare signext i16 @read_signed_index()
; Promote an i8 PHI to a width smaller than the scalar register width.
define i32 @phi_i8_to_i32(i8 %head) {
@@ -434,24 +435,25 @@ exit:
ret i16 %head
}
-; An existing sext user currently prevents promotion. Its signed result
-; must remain intact, particularly when the input is negative.
+; An existing sext user of a source no longer blocks signed promotion. Its
+; signed result must remain intact, including 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: [[SIGNED1:%.*]] = sext i16 [[HEAD]] to i64
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IDX:%.*]] = phi i16 [ [[HEAD]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
+; CHECK-NEXT: [[WIDE:%.*]] = phi i64 [ [[SIGNED]], %[[ENTRY]] ], [ 0, %[[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: [[AGAIN:%.*]] = icmp ne i64 [[WIDE]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
; CHECK: [[EXIT]]:
-; CHECK-NEXT: ret i64 [[SIGNED]]
+; CHECK-NEXT: ret i64 [[SIGNED1]]
;
entry:
%signed = sext i16 %head to i64
@@ -534,3 +536,214 @@ loop:
exit:
ret i64 %sum.next
}
+
+; Unsigned promotion must truncate the promoted source before applying an
+; existing sext, preserving negative results.
+define i64 @existing_sext_sink_unsigned(i16 %head) {
+; CHECK-LABEL: define i64 @existing_sext_sink_unsigned(
+; CHECK-SAME: i16 [[HEAD:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[TMP0]] to i16
+; CHECK-NEXT: [[SIGNED:%.*]] = sext i16 [[TMP1]] to i64
+; 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 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 i16 %idx to i64
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ ret i64 %signed
+}
+
+; A signext call return can be a source for signed promotion.
+define i64 @phi_call_source_signext() {
+; CHECK-LABEL: define i64 @phi_call_source_signext() {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[HEAD:%.*]] = call signext i16 @read_signed_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 signext i16 @read_signed_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
+}
+
+; The same signext call can feed unsigned promotion. The call attribute
+; does not replace the explicit zero extension of its narrow result.
+define i64 @phi_call_source_signext_unsigned() {
+; CHECK-LABEL: define i64 @phi_call_source_signext_unsigned() {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[HEAD:%.*]] = call signext i16 @read_signed_index()
+; 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: [[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 signext i16 @read_signed_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 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 sext user of the promoted PHI is redundant in signed mode.
+define i64 @phi_sext_sink(i16 %head) {
+; CHECK-LABEL: define i64 @phi_sext_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: [[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 [ [[IDX]], %[[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 ]
+ %signed = sext i16 %idx to i64
+ %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, %signed
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %signed, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
+
+; In unsigned mode, a sext user of the PHI needs truncation back to i16
+; before sign extension, even if another use zero extends the same PHI.
+define i64 @phi_sext_sink_unsigned(i16 %head) {
+; CHECK-LABEL: define i64 @phi_sext_sink_unsigned(
+; 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: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
+; CHECK-NEXT: [[SIGNED:%.*]] = sext i16 [[TMP1]] to i64
+; 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: [[SUM_NEXT]] = add i64 [[SUM]], [[SIGNED]]
+; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
+; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ [[SIGNED]], %[[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 ]
+ %signed = sext i16 %idx to i64
+ %negative = icmp slt i16 %idx, 0
+ br i1 %negative, label %exit, label %body
+
+body:
+ %wide = zext i16 %idx to i64
+ %sum.next = add i64 %sum, %signed
+ %again = icmp ne i64 %wide, 0
+ br i1 %again, label %loop, label %exit
+
+exit:
+ %result = phi i64 [ %signed, %loop ], [ %sum.next, %body ]
+ ret i64 %result
+}
>From 0e3246e03588f45f9a4824ca93679b121e260529 Mon Sep 17 00:00:00 2001
From: Ananth Jasty <ananth at openrv64.org>
Date: Fri, 25 Sep 2026 16:12:31 +0000
Subject: [PATCH 11/15] [CodeGen] Updated signed TypePromotion to confirm
extension matches sign before allowing trivial promotion.
---
llvm/lib/CodeGen/TypePromotion.cpp | 28 ++++++++++++++--------------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/llvm/lib/CodeGen/TypePromotion.cpp b/llvm/lib/CodeGen/TypePromotion.cpp
index 947a10c9f7145..85e6fd19b71ce 100644
--- a/llvm/lib/CodeGen/TypePromotion.cpp
+++ b/llvm/lib/CodeGen/TypePromotion.cpp
@@ -148,6 +148,7 @@ class TypePromotionImpl {
SmallPtrSet<Instruction *, 8> SafeToPromote;
SmallPtrSet<Instruction *, 4> SafeWrap;
SmallPtrSet<Instruction *, 4> InstsToRemove;
+ bool UseSExt;
// Does V have the same size result type as TypeSize.
bool EqualTypeSize(Value *V);
@@ -176,9 +177,8 @@ class TypePromotionImpl {
bool isSupportedValue(Value *V);
// Is V an instruction thats result can trivially promoted, or has safe
// wrapping.
- bool isLegalToPromote(Value *V, bool UseSExt = false);
- bool TryToPromote(Value *V, unsigned PromotedWidth, const LoopInfo &LI,
- bool UseSExt = false);
+ bool isLegalToPromote(Value *V);
+ bool TryToPromote(Value *V, unsigned PromotedWidth, const LoopInfo &LI);
public:
bool run(Function &F, const TargetMachine *TM,
@@ -249,8 +249,7 @@ bool TypePromotionImpl::isSource(Value *V) {
else if (isa<LoadInst>(V))
return true;
else if (auto *Call = dyn_cast<CallInst>(V))
- return (Call->hasRetAttr(Attribute::AttrKind::ZExt) ||
- Call->hasRetAttr(Attribute::AttrKind::SExt));
+ return (Call->hasRetAttr(UseSExt ? Attribute::AttrKind::SExt : Attribute::AttrKind::ZExt));
else if (auto *Trunc = dyn_cast<TruncInst>(V))
return EqualTypeSize(Trunc);
return false;
@@ -826,9 +825,9 @@ bool TypePromotionImpl::isSupportedValue(Value *V) {
// TODO We should accept calls even if they don't have zeroext, as they
// can still be sinks.
auto *Call = cast<CallInst>(I);
- return isSupportedType(Call) &&
- (Call->hasRetAttr(Attribute::AttrKind::ZExt) ||
- Call->hasRetAttr(Attribute::AttrKind::SExt));
+ return isSupportedType(Call) && (UseSExt ?
+ Call->hasRetAttr(Attribute::AttrKind::SExt) :
+ Call->hasRetAttr(Attribute::AttrKind::ZExt));
}
}
} else if (isa<Constant>(V) && !isa<ConstantExpr>(V)) {
@@ -842,7 +841,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 UseSExt) {
+bool TypePromotionImpl::isLegalToPromote(Value *V) {
auto *I = dyn_cast<Instruction>(V);
if (!I)
return true;
@@ -858,14 +857,14 @@ bool TypePromotionImpl::isLegalToPromote(Value *V, bool UseSExt) {
}
bool TypePromotionImpl::TryToPromote(Value *V, unsigned PromotedWidth,
- const LoopInfo &LI, bool UseSExt) {
+ const LoopInfo &LI) {
Type *OrigTy = V->getType();
TypeSize = OrigTy->getPrimitiveSizeInBits().getFixedValue();
SafeToPromote.clear();
SafeWrap.clear();
if (!isSupportedValue(V) || !shouldPromote(V) ||
- !isLegalToPromote(V, UseSExt))
+ !isLegalToPromote(V))
return false;
LLVM_DEBUG(dbgs() << "IR Promotion: TryToPromote: " << *V << ", from "
@@ -890,7 +889,7 @@ bool TypePromotionImpl::TryToPromote(Value *V, unsigned PromotedWidth,
return false;
if (!isSupportedValue(V) ||
- (shouldPromote(V) && !isLegalToPromote(V, UseSExt))) {
+ (shouldPromote(V) && !isLegalToPromote(V))) {
LLVM_DEBUG(dbgs() << "IR Promotion: Can't handle: " << *V << "\n");
return false;
}
@@ -1047,6 +1046,7 @@ bool TypePromotionImpl::run(Function &F, const TargetMachine *TM,
for (BasicBlock &BB : F) {
for (Instruction &I : BB) {
+ UseSExt = false;
if (AllVisited.count(&I))
continue;
@@ -1055,7 +1055,7 @@ bool TypePromotionImpl::run(Function &F, const TargetMachine *TM,
LLVM_DEBUG(dbgs() << "IR Promotion: Searching from: "
<< *I.getOperand(0) << "\n");
auto *ZExt = cast<ZExtInst>(&I);
- bool UseSExt = ZExt->hasNonNeg();
+ UseSExt = ZExt->hasNonNeg();
EVT ZExtVT = TLI->getValueType(DL, I.getType());
Instruction *Phi = static_cast<Instruction *>(I.getOperand(0));
auto PromoteWidth = ZExtVT.getFixedSizeInBits();
@@ -1064,7 +1064,7 @@ bool TypePromotionImpl::run(Function &F, const TargetMachine *TM,
<< "register for ZExt type\n");
continue;
}
- MadeChange |= TryToPromote(Phi, PromoteWidth, LI, UseSExt);
+ MadeChange |= TryToPromote(Phi, PromoteWidth, LI);
} else if (auto *ICmp = dyn_cast<ICmpInst>(&I)) {
// Search up from icmps to try to promote their operands.
// Skip signed or pointer compares
>From 143d81c266840db6928d30050c16df05d798eb8f Mon Sep 17 00:00:00 2001
From: Ananth Jasty <ananth at openrv64.org>
Date: Fri, 25 Sep 2026 16:14:15 +0000
Subject: [PATCH 12/15] [CodeGen] Code formatting.
---
llvm/lib/CodeGen/TypePromotion.cpp | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/llvm/lib/CodeGen/TypePromotion.cpp b/llvm/lib/CodeGen/TypePromotion.cpp
index 85e6fd19b71ce..4a89e047e0204 100644
--- a/llvm/lib/CodeGen/TypePromotion.cpp
+++ b/llvm/lib/CodeGen/TypePromotion.cpp
@@ -249,7 +249,8 @@ bool TypePromotionImpl::isSource(Value *V) {
else if (isa<LoadInst>(V))
return true;
else if (auto *Call = dyn_cast<CallInst>(V))
- return (Call->hasRetAttr(UseSExt ? Attribute::AttrKind::SExt : Attribute::AttrKind::ZExt));
+ return (Call->hasRetAttr(UseSExt ? Attribute::AttrKind::SExt
+ : Attribute::AttrKind::ZExt));
else if (auto *Trunc = dyn_cast<TruncInst>(V))
return EqualTypeSize(Trunc);
return false;
@@ -825,9 +826,9 @@ bool TypePromotionImpl::isSupportedValue(Value *V) {
// TODO We should accept calls even if they don't have zeroext, as they
// can still be sinks.
auto *Call = cast<CallInst>(I);
- return isSupportedType(Call) && (UseSExt ?
- Call->hasRetAttr(Attribute::AttrKind::SExt) :
- Call->hasRetAttr(Attribute::AttrKind::ZExt));
+ return isSupportedType(Call) &&
+ (UseSExt ? Call->hasRetAttr(Attribute::AttrKind::SExt)
+ : Call->hasRetAttr(Attribute::AttrKind::ZExt));
}
}
} else if (isa<Constant>(V) && !isa<ConstantExpr>(V)) {
@@ -863,8 +864,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))
return false;
LLVM_DEBUG(dbgs() << "IR Promotion: TryToPromote: " << *V << ", from "
@@ -888,8 +888,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))) {
LLVM_DEBUG(dbgs() << "IR Promotion: Can't handle: " << *V << "\n");
return false;
}
>From e6cd4845b3e70eb7c92db8c34844842e512e8445 Mon Sep 17 00:00:00 2001
From: Ananth Jasty <ananth at openrv64.org>
Date: Fri, 25 Sep 2026 16:16:03 +0000
Subject: [PATCH 13/15] [CodeGen] Updated test to catch mismatched sign
extension.
Assisted-by: Codex
---
.../AArch64/phi-zext-nneg-sources-sinks.ll | 60 +++++++++++++++----
1 file changed, 50 insertions(+), 10 deletions(-)
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
index 8d7752d96db07..db45536fec669 100644
--- a/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg-sources-sinks.ll
+++ b/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg-sources-sinks.ll
@@ -216,21 +216,20 @@ exit:
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.
+; A zeroext call does not match signed promotion. Leave the loop narrow
+; rather than introducing a sign extension of the return value.
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: [[TMP1:%.*]] = phi i16 [ [[HEAD]], %[[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]]
@@ -618,21 +617,20 @@ exit:
ret i64 %result
}
-; The same signext call can feed unsigned promotion. The call attribute
-; does not replace the explicit zero extension of its narrow result.
+; A signext call does not match unsigned promotion. Leave the loop narrow
+; rather than introducing a zero extension of the return value.
define i64 @phi_call_source_signext_unsigned() {
; CHECK-LABEL: define i64 @phi_call_source_signext_unsigned() {
; CHECK-NEXT: [[ENTRY:.*]]:
; CHECK-NEXT: [[HEAD:%.*]] = call signext i16 @read_signed_index()
-; 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:%.*]] = phi i16 [ [[HEAD]], %[[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 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]]
@@ -747,3 +745,45 @@ exit:
%result = phi i64 [ %signed, %loop ], [ %sum.next, %body ]
ret i64 %result
}
+
+; A zeroext call matches unsigned promotion and can supply the widened PHI.
+define i64 @phi_call_source_zeroext_unsigned() {
+; CHECK-LABEL: define i64 @phi_call_source_zeroext_unsigned() {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[HEAD:%.*]] = call zeroext i16 @read_index()
+; 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: [[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 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
+}
>From 6028a00b0115a610888c3df20171c96e214cddec Mon Sep 17 00:00:00 2001
From: Ananth Jasty <ananth at openrv64.org>
Date: Sat, 26 Sep 2026 19:59:30 +0000
Subject: [PATCH 14/15] [CodeGen] TypePromotion confirms sext sinks are only
active when UseSExt is selected at the outset.
---
llvm/lib/CodeGen/TypePromotion.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/lib/CodeGen/TypePromotion.cpp b/llvm/lib/CodeGen/TypePromotion.cpp
index 4a89e047e0204..f89a0797dea1e 100644
--- a/llvm/lib/CodeGen/TypePromotion.cpp
+++ b/llvm/lib/CodeGen/TypePromotion.cpp
@@ -277,7 +277,7 @@ bool TypePromotionImpl::isSink(Value *V) {
if (auto *ZExt = dyn_cast<ZExtInst>(V))
return GreaterThanTypeSize(ZExt);
if (auto *SExt = dyn_cast<SExtInst>(V))
- return GreaterThanTypeSize(SExt);
+ return UseSExt && GreaterThanTypeSize(SExt);
if (auto *Switch = dyn_cast<SwitchInst>(V))
return LessThanTypeSize(Switch->getCondition());
if (auto *ICmp = dyn_cast<ICmpInst>(V))
>From b2e235004d133249801896cca8606643bf63325b Mon Sep 17 00:00:00 2001
From: Ananth Jasty <ananth at openrv64.org>
Date: Sat, 26 Sep 2026 20:00:36 +0000
Subject: [PATCH 15/15] [CodeGen][Tests] UUpdated test expectations to match
sext sink modes.
Assisted-by: Codex
---
.../AArch64/phi-zext-nneg-sources-sinks.ll | 23 ++++++++-----------
1 file changed, 9 insertions(+), 14 deletions(-)
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
index db45536fec669..8084f3ea59c91 100644
--- a/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg-sources-sinks.ll
+++ b/llvm/test/Transforms/TypePromotion/AArch64/phi-zext-nneg-sources-sinks.ll
@@ -536,22 +536,19 @@ exit:
ret i64 %sum.next
}
-; Unsigned promotion must truncate the promoted source before applying an
-; existing sext, preserving negative results.
+; An existing sext user of the source prevents unsigned promotion.
define i64 @existing_sext_sink_unsigned(i16 %head) {
; CHECK-LABEL: define i64 @existing_sext_sink_unsigned(
; CHECK-SAME: i16 [[HEAD:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[HEAD]] to i64
-; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[TMP0]] to i16
-; CHECK-NEXT: [[SIGNED:%.*]] = sext i16 [[TMP1]] to i64
+; CHECK-NEXT: [[SIGNED:%.*]] = sext i16 [[HEAD]] to i64
; 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: [[TMP2:%.*]] = phi i16 [ [[HEAD]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP2]], 0
; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
; CHECK: [[BODY]]:
+; CHECK-NEXT: [[IDX:%.*]] = zext i16 [[TMP2]] to i64
; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
; CHECK: [[EXIT]]:
@@ -701,23 +698,21 @@ exit:
ret i64 %result
}
-; In unsigned mode, a sext user of the PHI needs truncation back to i16
-; before sign extension, even if another use zero extends the same PHI.
+; A sext user of the PHI prevents unsigned promotion, even if another use
+; zero extends the same PHI.
define i64 @phi_sext_sink_unsigned(i16 %head) {
; CHECK-LABEL: define i64 @phi_sext_sink_unsigned(
; 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:%.*]] = phi i16 [ [[HEAD]], %[[ENTRY]] ], [ 0, %[[BODY:.*]] ]
; CHECK-NEXT: [[SUM:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[BODY]] ]
-; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[IDX]] to i16
; CHECK-NEXT: [[SIGNED:%.*]] = sext i16 [[TMP1]] to i64
-; CHECK-NEXT: [[TMP2:%.*]] = trunc i64 [[IDX]] to i16
-; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP2]], 0
+; CHECK-NEXT: [[NEGATIVE:%.*]] = icmp slt i16 [[TMP1]], 0
; CHECK-NEXT: br i1 [[NEGATIVE]], label %[[EXIT:.*]], label %[[BODY]]
; CHECK: [[BODY]]:
+; CHECK-NEXT: [[IDX:%.*]] = zext i16 [[TMP1]] to i64
; CHECK-NEXT: [[SUM_NEXT]] = add i64 [[SUM]], [[SIGNED]]
; CHECK-NEXT: [[AGAIN:%.*]] = icmp ne i64 [[IDX]], 0
; CHECK-NEXT: br i1 [[AGAIN]], label %[[LOOP]], label %[[EXIT]]
More information about the llvm-commits
mailing list