[llvm] [AggressiveInstCombine] Fix crash when folding consecutive loads into a type smaller than the combined load (PR #207229)
Kyungtak Woo via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 2 09:55:42 PDT 2026
https://github.com/kevinwkt created https://github.com/llvm/llvm-project/pull/207229
This patch fixes a compiler crash in AggressiveInstCombine during the foldConsecutiveLoads pass.
When combining multiple consecutive narrow loads (ex: `i8`) into a single wider load (ex: `i32`), the pass determines the target type based on the type of the `or` chain. If the original IR contains shifts that are greater than or equal to the bitwidth of the target type, the combined load size can end up being larger than the target type. These shifts are technically out-of-bounds and result in poison, but they are still valid IR that the compiler must handle.
In this scenario, the pass currently attempts to zero-extend the wider combined load to the smaller target type (for example, `zext i32 %load to i16`). This violates LLVM IR invariants and triggers the following assertion in ZExtInst:
```
assert.h assertion failed at llvm/lib/IR/Instructions.cpp:3490 in llvm::ZExtInst::ZExtInst(...): castIsValid(getOpcode(), S, Ty) && "Illegal ZExt"
```
In release builds (where assertions are disabled), this results in the compiler generating invalid IR.
To fix this, we replaced the call to Builder.CreateZExt with Builder.CreateZExtOrTrunc.
If the combined load is wider than the target type, it will now be truncated. This is semantically correct: the out-of-bounds shifted parts of the load were already poison in the original IR, so truncating the wider load to fit the target type should be a valid refinement of that poison.
Reproducer:
```
define i16 @combine_four_i8_loads_i16_poison(ptr %p) {
%p1 = getelementptr i8, ptr %p, i64 1
%p2 = getelementptr i8, ptr %p, i64 2
%p3 = getelementptr i8, ptr %p, i64 3
%l1 = load i8, ptr %p
%l2 = load i8, ptr %p1
%l3 = load i8, ptr %p2
%l4 = load i8, ptr %p3
%z1 = zext i8 %l1 to i16
%z2 = zext i8 %l2 to i16
%z3 = zext i8 %l3 to i16
%z4 = zext i8 %l4 to i16
%sh2 = shl i16 %z2, 8
%or1 = or i16 %z1, %sh2
; The following shifts are out-of-bounds for i16 and result in poison
%sh3 = shl i16 %z3, 16
%or2 = or i16 %or1, %sh3
%sh4 = shl i16 %z4, 24
%or3 = or i16 %or2, %sh4
ret i16 %or3
}
```
Running `opt -passes=aggressive-instcombine` on this IR will trigger the assertion before this patch, and will correctly output a trunc of a load i32 after this patch.
>From 674ed2365b0f41c394f8507b134b90ec9a6dade2 Mon Sep 17 00:00:00 2001
From: Kyungtak Woo <kevinwkt at google.com>
Date: Thu, 2 Jul 2026 16:45:06 +0000
Subject: [PATCH] fix: use CreateZExtOrTrunc in AggressiveInstCombine to
support truncation during load combination
---
.../AggressiveInstCombine.cpp | 2 +-
.../AggressiveInstCombine/X86/or-load.ll | 71 ++++++++++++++++---
2 files changed, 63 insertions(+), 10 deletions(-)
diff --git a/llvm/lib/Transforms/AggressiveInstCombine/AggressiveInstCombine.cpp b/llvm/lib/Transforms/AggressiveInstCombine/AggressiveInstCombine.cpp
index 59b00ea5e8018..4c57e830f64c6 100644
--- a/llvm/lib/Transforms/AggressiveInstCombine/AggressiveInstCombine.cpp
+++ b/llvm/lib/Transforms/AggressiveInstCombine/AggressiveInstCombine.cpp
@@ -1434,7 +1434,7 @@ static bool foldConsecutiveLoads(Instruction &I, const DataLayout &DL,
Value *NewOp = NewLoad;
// Check if zero extend needed.
if (LOps.ZextType)
- NewOp = Builder.CreateZExt(NewOp, LOps.ZextType);
+ NewOp = Builder.CreateZExtOrTrunc(NewOp, LOps.ZextType);
// Check if shift needed. We need to shift with the amount of load1
// shift if not zero.
diff --git a/llvm/test/Transforms/AggressiveInstCombine/X86/or-load.ll b/llvm/test/Transforms/AggressiveInstCombine/X86/or-load.ll
index 73ff729900058..e30c30f2e364e 100644
--- a/llvm/test/Transforms/AggressiveInstCombine/X86/or-load.ll
+++ b/llvm/test/Transforms/AggressiveInstCombine/X86/or-load.ll
@@ -1377,19 +1377,19 @@ entry:
define i32 @loadCombine_4consecutive_metadata(ptr %p, ptr %pstr) {
; LE-LABEL: @loadCombine_4consecutive_metadata(
-; LE-NEXT: [[L1:%.*]] = load i32, ptr [[P:%.*]], align 1, !alias.scope [[META0:![0-9]+]]
-; LE-NEXT: store i32 25, ptr [[PSTR:%.*]], align 4, !noalias [[META0]]
+; LE-NEXT: [[L1:%.*]] = load i32, ptr [[P:%.*]], align 1, !alias.scope [[META1:![0-9]+]]
+; LE-NEXT: store i32 25, ptr [[PSTR:%.*]], align 4, !noalias [[META1]]
; LE-NEXT: ret i32 [[L1]]
;
; BE-LABEL: @loadCombine_4consecutive_metadata(
; BE-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[P:%.*]], i32 1
; BE-NEXT: [[P2:%.*]] = getelementptr i8, ptr [[P]], i32 2
; BE-NEXT: [[P3:%.*]] = getelementptr i8, ptr [[P]], i32 3
-; BE-NEXT: [[L1:%.*]] = load i8, ptr [[P]], align 1, !alias.scope [[META0:![0-9]+]]
-; BE-NEXT: [[L2:%.*]] = load i8, ptr [[P1]], align 1, !alias.scope [[META0]]
-; BE-NEXT: [[L3:%.*]] = load i8, ptr [[P2]], align 1, !alias.scope [[META0]]
-; BE-NEXT: [[L4:%.*]] = load i8, ptr [[P3]], align 1, !alias.scope [[META0]]
-; BE-NEXT: store i32 25, ptr [[PSTR:%.*]], align 4, !noalias [[META0]]
+; BE-NEXT: [[L1:%.*]] = load i8, ptr [[P]], align 1, !alias.scope [[META1:![0-9]+]]
+; BE-NEXT: [[L2:%.*]] = load i8, ptr [[P1]], align 1, !alias.scope [[META1]]
+; BE-NEXT: [[L3:%.*]] = load i8, ptr [[P2]], align 1, !alias.scope [[META1]]
+; BE-NEXT: [[L4:%.*]] = load i8, ptr [[P3]], align 1, !alias.scope [[META1]]
+; BE-NEXT: store i32 25, ptr [[PSTR:%.*]], align 4, !noalias [[META1]]
; BE-NEXT: [[E1:%.*]] = zext i8 [[L1]] to i32
; BE-NEXT: [[E2:%.*]] = zext i8 [[L2]] to i32
; BE-NEXT: [[E3:%.*]] = zext i8 [[L3]] to i32
@@ -2223,7 +2223,7 @@ define i32 @loadCombine_4consecutive_badinsert4(ptr %p) {
; LE-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 1
; LE-NEXT: [[C1:%.*]] = load i8, ptr [[P1]], align 1
; LE-NEXT: [[CMP:%.*]] = icmp eq i8 [[C1]], 0
-; LE-NEXT: br i1 [[CMP]], label [[END:%.*]], label [[BB2:%.*]]
+; LE-NEXT: br i1 [[CMP]], label [[END:%.*]], label [[BB2:%.*]], !prof [[PROF4:![0-9]+]]
; LE: bb2:
; LE-NEXT: [[L1:%.*]] = load i32, ptr [[P1]], align 1
; LE-NEXT: br label [[END]]
@@ -2236,7 +2236,7 @@ define i32 @loadCombine_4consecutive_badinsert4(ptr %p) {
; BE-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 1
; BE-NEXT: [[C1:%.*]] = load i8, ptr [[P1]], align 1
; BE-NEXT: [[CMP:%.*]] = icmp eq i8 [[C1]], 0
-; BE-NEXT: br i1 [[CMP]], label [[END:%.*]], label [[BB2:%.*]]
+; BE-NEXT: br i1 [[CMP]], label [[END:%.*]], label [[BB2:%.*]], !prof [[PROF4:![0-9]+]]
; BE: bb2:
; BE-NEXT: [[L1:%.*]] = load i8, ptr [[P1]], align 1
; BE-NEXT: [[C2:%.*]] = zext i8 [[L1]] to i32
@@ -2629,3 +2629,56 @@ entry:
%res = lshr i64 %or2, 32
ret i64 %res
}
+
+define i16 @combine_four_i8_loads_i16_poison(ptr %p) {
+; LE-LABEL: @combine_four_i8_loads_i16_poison(
+; LE-NEXT: [[P2:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 2
+; LE-NEXT: [[P3:%.*]] = getelementptr i8, ptr [[P]], i64 3
+; LE-NEXT: [[L1:%.*]] = load i32, ptr [[P]], align 1
+; LE-NEXT: [[TMP1:%.*]] = trunc i32 [[L1]] to i16
+; LE-NEXT: [[L3:%.*]] = load i8, ptr [[P2]], align 1
+; LE-NEXT: [[L4:%.*]] = load i8, ptr [[P3]], align 1
+; LE-NEXT: [[Z3:%.*]] = zext i8 [[L3]] to i16
+; LE-NEXT: [[Z4:%.*]] = zext i8 [[L4]] to i16
+; LE-NEXT: ret i16 [[TMP1]]
+;
+; BE-LABEL: @combine_four_i8_loads_i16_poison(
+; BE-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 1
+; BE-NEXT: [[P2:%.*]] = getelementptr i8, ptr [[P]], i64 2
+; BE-NEXT: [[P3:%.*]] = getelementptr i8, ptr [[P]], i64 3
+; BE-NEXT: [[L1:%.*]] = load i8, ptr [[P]], align 1
+; BE-NEXT: [[L2:%.*]] = load i8, ptr [[P1]], align 1
+; BE-NEXT: [[L3:%.*]] = load i8, ptr [[P2]], align 1
+; BE-NEXT: [[L4:%.*]] = load i8, ptr [[P3]], align 1
+; BE-NEXT: [[Z1:%.*]] = zext i8 [[L1]] to i16
+; BE-NEXT: [[Z2:%.*]] = zext i8 [[L2]] to i16
+; BE-NEXT: [[Z3:%.*]] = zext i8 [[L3]] to i16
+; BE-NEXT: [[Z4:%.*]] = zext i8 [[L4]] to i16
+; BE-NEXT: [[SH2:%.*]] = shl i16 [[Z2]], 8
+; BE-NEXT: [[OR1:%.*]] = or i16 [[Z1]], [[SH2]]
+; BE-NEXT: [[SH3:%.*]] = shl i16 [[Z3]], 16
+; BE-NEXT: [[OR2:%.*]] = or i16 [[OR1]], [[SH3]]
+; BE-NEXT: [[SH4:%.*]] = shl i16 [[Z4]], 24
+; BE-NEXT: [[OR3:%.*]] = or i16 [[OR2]], [[SH4]]
+; BE-NEXT: ret i16 [[OR3]]
+;
+ %p1 = getelementptr i8, ptr %p, i64 1
+ %p2 = getelementptr i8, ptr %p, i64 2
+ %p3 = getelementptr i8, ptr %p, i64 3
+ %l1 = load i8, ptr %p
+ %l2 = load i8, ptr %p1
+ %l3 = load i8, ptr %p2
+ %l4 = load i8, ptr %p3
+ %z1 = zext i8 %l1 to i16
+ %z2 = zext i8 %l2 to i16
+ %z3 = zext i8 %l3 to i16
+ %z4 = zext i8 %l4 to i16
+ %sh2 = shl i16 %z2, 8
+ %or1 = or i16 %z1, %sh2
+ %sh3 = shl i16 %z3, 16
+ %or2 = or i16 %or1, %sh3
+ %sh4 = shl i16 %z4, 24
+ %or3 = or i16 %or2, %sh4
+ ret i16 %or3
+}
+
More information about the llvm-commits
mailing list