[llvm] [ConstantFolding] Fix bitcasting vectors with non-integer ratios (PR #179640)
Kacper Doga via llvm-commits
llvm-commits at lists.llvm.org
Fri Mar 6 07:11:31 PST 2026
https://github.com/varev-dev updated https://github.com/llvm/llvm-project/pull/179640
>From 71348c0a994d22486741d3524feee99c30840e30 Mon Sep 17 00:00:00 2001
From: "Doga, Kacper" <kacper.doga at intel.com>
Date: Wed, 4 Feb 2026 09:37:02 +0000
Subject: [PATCH] Support bitcasting vectors with non-integer ratios.
---
llvm/lib/Analysis/ConstantFolding.cpp | 149 ++++++++++++------
llvm/test/Transforms/InstCombine/cast.ll | 4 +-
.../InstSimplify/bitcast-vector-fold.ll | 65 +++++++-
.../SCCP/bitcast-vector-refinement.l.ll | 3 +-
4 files changed, 168 insertions(+), 53 deletions(-)
diff --git a/llvm/lib/Analysis/ConstantFolding.cpp b/llvm/lib/Analysis/ConstantFolding.cpp
index 7573afe423ec9..d90c15a5825d9 100644
--- a/llvm/lib/Analysis/ConstantFolding.cpp
+++ b/llvm/lib/Analysis/ConstantFolding.cpp
@@ -217,81 +217,138 @@ Constant *FoldBitCast(Constant *C, Type *DestTy, const DataLayout &DL) {
// conversion here, which depends on whether the input or output has
// more elements.
bool isLittleEndian = DL.isLittleEndian();
-
+ unsigned SrcBitSize = SrcEltTy->getPrimitiveSizeInBits();
+ unsigned DstBitSize = DL.getTypeSizeInBits(DstEltTy);
SmallVector<Constant*, 32> Result;
+ unsigned SrcElt = 0;
+ APInt Rest(std::max(SrcBitSize, DstBitSize), 0);
+ unsigned RestBitSize = 0;
+ bool HasUndef = true;
+ bool HasPoison = false;
+
if (NumDstElt < NumSrcElt) {
// Handle: bitcast (<4 x i32> <i32 0, i32 1, i32 2, i32 3> to <2 x i64>)
- Constant *Zero = Constant::getNullValue(DstEltTy);
- unsigned Ratio = NumSrcElt/NumDstElt;
- unsigned SrcBitSize = SrcEltTy->getPrimitiveSizeInBits();
- unsigned SrcElt = 0;
- for (unsigned i = 0; i != NumDstElt; ++i) {
- // Build each element of the result.
- Constant *Elt = Zero;
- unsigned ShiftAmt = isLittleEndian ? 0 : SrcBitSize*(Ratio-1);
- for (unsigned j = 0; j != Ratio; ++j) {
- Constant *Src = C->getAggregateElement(SrcElt++);
- if (isa_and_nonnull<UndefValue>(Src))
- Src = Constant::getNullValue(
- cast<VectorType>(C->getType())->getElementType());
- else
- Src = dyn_cast_or_null<ConstantInt>(Src);
- if (!Src) // Reject constantexpr elements.
+ // bitcast (<4 x i24> <i24 0, i24 1, i24 2, i24 3> to <3 x i32>)
+ APInt Zero = APInt::getZero(DstBitSize);
+ while (Result.size() != NumDstElt) {
+ APInt NextVecElem;
+ if (!HasUndef)
+ NextVecElem = Zero;
+ while (RestBitSize < DstBitSize) {
+ assert(SrcElt < NumSrcElt && "Source vector overflow.");
+ auto *Element = C->getAggregateElement(SrcElt++);
+ if (!Element) // Reject constantexpr elements.
return ConstantExpr::getBitCast(C, DestTy);
- // Zero extend the element to the right size.
- Src = ConstantFoldCastOperand(Instruction::ZExt, Src, Elt->getType(),
- DL);
- assert(Src && "Constant folding cannot fail on plain integers");
+ RestBitSize += SrcBitSize;
+ HasUndef = isa<UndefValue>(Element);
+ if (HasUndef) {
+ HasPoison |= isa<PoisonValue>(Element);
+ continue;
+ }
- // Shift it to the right place, depending on endianness.
- Src = ConstantFoldBinaryOpOperands(
- Instruction::Shl, Src, ConstantInt::get(Src->getType(), ShiftAmt),
- DL);
- assert(Src && "Constant folding cannot fail on plain integers");
+ auto *Src = dyn_cast<ConstantInt>(Element);
+ if (!Src)
+ return ConstantExpr::getBitCast(C, DestTy);
+ NextVecElem = Src->getValue();
+ NextVecElem = NextVecElem.zext(DstBitSize);
- ShiftAmt += isLittleEndian ? SrcBitSize : -SrcBitSize;
+ // Shift next SrcElt to right place, depending on endianness.
+ if (isLittleEndian) {
+ Rest |= NextVecElem << RestBitSize - SrcBitSize;
+ } else {
+ if (RestBitSize <= DstBitSize)
+ Rest |= NextVecElem << (DstBitSize - RestBitSize);
+ else
+ Rest |= NextVecElem.lshr(RestBitSize - DstBitSize);
+ }
+ }
- // Mix it in.
- Elt = ConstantFoldBinaryOpOperands(Instruction::Or, Elt, Src, DL);
- assert(Elt && "Constant folding cannot fail on plain integers");
+ RestBitSize -= DstBitSize;
+ if (NextVecElem.getBitWidth() != DstBitSize) {
+ if (HasPoison)
+ Result.push_back(PoisonValue::get(DstEltTy));
+ else
+ Result.push_back(UndefValue::get(DstEltTy));
+ continue;
}
- Result.push_back(Elt);
+ Result.push_back(ConstantInt::get(DstEltTy, Rest));
+
+ // Shift unused bits from last SrcElt to next DstElt right place.
+ if (isLittleEndian)
+ Rest = NextVecElem.lshr(SrcBitSize - RestBitSize);
+ else
+ Rest = NextVecElem << (DstBitSize - RestBitSize);
}
return ConstantVector::get(Result);
}
// Handle: bitcast (<2 x i64> <i64 0, i64 1> to <4 x i32>)
+ // bitcast (<3 x i64> <i64 0, i64 1, i64 2> to <8 x i24>)
unsigned Ratio = NumDstElt/NumSrcElt;
- unsigned DstBitSize = DL.getTypeSizeInBits(DstEltTy);
-
- // Loop over each source value, expanding into multiple results.
- for (unsigned i = 0; i != NumSrcElt; ++i) {
- auto *Element = C->getAggregateElement(i);
-
+ while (Result.size() != NumDstElt) {
+ unsigned UnusedBits = SrcBitSize - RestBitSize;
+ if (RestBitSize >= DstBitSize) {
+ if (!HasUndef) {
+ APInt Elt =
+ Rest.lshr(isLittleEndian ? UnusedBits : (RestBitSize - DstBitSize));
+ Result.push_back(ConstantInt::get(DstEltTy, Elt.trunc(DstBitSize)));
+ } else if (HasPoison) {
+ Result.push_back(PoisonValue::get(DstEltTy));
+ HasPoison = RestBitSize > DstBitSize;
+ } else {
+ Result.push_back(UndefValue::get(DstEltTy));
+ HasUndef = RestBitSize > DstBitSize;
+ }
+ RestBitSize -= DstBitSize;
+ continue;
+ }
+ auto *Element = C->getAggregateElement(SrcElt++);
if (!Element) // Reject constantexpr elements.
return ConstantExpr::getBitCast(C, DestTy);
+ APInt NextVecElem;
if (isa<UndefValue>(Element)) {
// Correctly Propagate undef values.
- Result.append(Ratio, UndefValue::get(DstEltTy));
- continue;
+ HasUndef = true;
+ HasPoison = isa<PoisonValue>(Element);
+ if (RestBitSize == 0) {
+ RestBitSize += SrcBitSize;
+ continue;
+ }
+ NextVecElem = APInt::getZero(SrcBitSize);
+ } else {
+ auto *Src = dyn_cast<ConstantInt>(Element);
+ if (!Src)
+ return ConstantExpr::getBitCast(C, DestTy);
+ NextVecElem = Src->getValue();
+ HasUndef = false;
}
-
- auto *Src = dyn_cast<ConstantInt>(Element);
- if (!Src)
- return ConstantExpr::getBitCast(C, DestTy);
-
- unsigned ShiftAmt = isLittleEndian ? 0 : DstBitSize*(Ratio-1);
+ APInt Value = Rest;
+ if (SrcBitSize % DstBitSize)
+ Rest = NextVecElem;
+ if (RestBitSize != 0) {
+ // Shift the Rest into the right place, shift NextVecElem to fit Rest.
+ if (isLittleEndian) {
+ Value.lshrInPlace(UnusedBits);
+ NextVecElem <<= RestBitSize;
+ } else {
+ Value <<= UnusedBits;
+ NextVecElem.lshrInPlace(RestBitSize);
+ }
+ }
+ Value |= NextVecElem;
+ unsigned ShiftAmt = isLittleEndian ? 0 : SrcBitSize - DstBitSize;
for (unsigned j = 0; j != Ratio; ++j) {
// Shift the piece of the value into the right place, depending on
// endianness.
- APInt Elt = Src->getValue().lshr(ShiftAmt);
+ APInt Elt = Value.lshr(ShiftAmt);
ShiftAmt += isLittleEndian ? DstBitSize : -DstBitSize;
// Truncate and remember this piece.
Result.push_back(ConstantInt::get(DstEltTy, Elt.trunc(DstBitSize)));
}
+ RestBitSize += SrcBitSize - Ratio * DstBitSize;
}
return ConstantVector::get(Result);
diff --git a/llvm/test/Transforms/InstCombine/cast.ll b/llvm/test/Transforms/InstCombine/cast.ll
index 46deb294b9d45..130fdec6fdb05 100644
--- a/llvm/test/Transforms/InstCombine/cast.ll
+++ b/llvm/test/Transforms/InstCombine/cast.ll
@@ -1438,10 +1438,10 @@ define i32 @test89() {
define <2 x i32> @test90() {
; BE-LABEL: @test90(
-; BE-NEXT: ret <2 x i32> <i32 0, i32 15360>
+; BE-NEXT: ret <2 x i32> <i32 poison, i32 15360>
;
; LE-LABEL: @test90(
-; LE-NEXT: ret <2 x i32> <i32 0, i32 1006632960>
+; LE-NEXT: ret <2 x i32> <i32 poison, i32 1006632960>
;
%t6 = bitcast <4 x half> <half poison, half poison, half poison, half 0xH3C00> to <2 x i32>
ret <2 x i32> %t6
diff --git a/llvm/test/Transforms/InstSimplify/bitcast-vector-fold.ll b/llvm/test/Transforms/InstSimplify/bitcast-vector-fold.ll
index d2656e291547c..ede6db4dbc3e0 100644
--- a/llvm/test/Transforms/InstSimplify/bitcast-vector-fold.ll
+++ b/llvm/test/Transforms/InstSimplify/bitcast-vector-fold.ll
@@ -1,7 +1,7 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
-; RUN: opt < %s -passes=instsimplify -S | FileCheck %s
-; RUN: opt < %s -passes=instsimplify -use-constant-fp-for-fixed-length-splat -use-constant-int-for-fixed-length-splat -S | FileCheck %s
-target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-f64:32:64-v64:64:64-v128:128:128"
+; RUN: opt < %s -passes=instsimplify -S -data-layout="e-p:32:32:32-i1:8:8-i8:8:8-f64:32:64-v64:64:64-v128:128:128" | FileCheck %s --check-prefixes=CHECK
+; RUN: opt < %s -passes=instsimplify -use-constant-fp-for-fixed-length-splat -use-constant-int-for-fixed-length-splat -S -data-layout="e-p:32:32:32-i1:8:8-i8:8:8-f64:32:64-v64:64:64-v128:128:128" | FileCheck %s --check-prefixes=CHECK
+; RUN: opt < %s -passes=instsimplify -S -data-layout="E-p:32:32:32-i1:8:8-i8:8:8-f64:32:64-v64:64:64-v128:128:128" | FileCheck %s --check-prefixes=BE
define <2 x i64> @test1() {
; CHECK-LABEL: @test1(
@@ -83,6 +83,49 @@ define <1 x i1> @test10() {
ret <1 x i1> %ret
}
+define <4 x i24> @test11() {
+; CHECK-LABEL: @test11(
+; CHECK-NEXT: ret <4 x i24> <i24 1, i24 256, i24 65536, i24 0>
+;
+; BE-LABEL: @test11(
+; BE-NEXT: ret <4 x i24> <i24 0, i24 65536, i24 256, i24 1>
+;
+ %c = bitcast <3 x i32> <i32 1, i32 1, i32 1> to <4 x i24>
+ ret <4 x i24> %c
+}
+
+define <8 x i24> @test12() {
+; CHECK-LABEL: @test12(
+; CHECK-NEXT: ret <8 x i24> splat (i24 1)
+;
+; BE-LABEL: @test12(
+; BE-NEXT: ret <8 x i24> <i24 256, i24 256, i24 257, i24 1, i24 1, i24 0, i24 65536, i24 65536>
+ %c = bitcast <3 x i64> <i64 281474993487873, i64 72057598332895488, i64 1099511693312> to <8 x i24>
+ ret <8 x i24> %c
+}
+
+define <3 x i32> @test13() {
+; CHECK-LABEL: @test13(
+; CHECK-NEXT: ret <3 x i32> splat (i32 1)
+;
+; BE-LABEL: @test13(
+; BE-NEXT: ret <3 x i32> <i32 256, i32 16777472, i32 0>
+;
+ %c = bitcast <4 x i24> <i24 1, i24 256, i24 65536, i24 0> to <3 x i32>
+ ret <3 x i32> %c
+}
+
+define <3 x i64> @test14() {
+; CHECK-LABEL: @test14(
+; CHECK-NEXT: ret <3 x i64> <i64 281474993487873, i64 72057598332895488, i64 1099511693312>
+;
+; BE-LABEL: @test14(
+; BE-NEXT: ret <3 x i64> <i64 1099511693312, i64 72057598332895488, i64 281474993487873>
+;
+ %c = bitcast <8 x i24> splat (i24 1) to <3 x i64>
+ ret <3 x i64> %c
+}
+
; from MultiSource/Benchmarks/Bullet
define <2 x float> @foo() {
; CHECK-LABEL: @foo(
@@ -277,6 +320,22 @@ define <16 x i8> @bitcast_constexpr_16i8_8i16_u256uuu256uu() {
ret <16 x i8> %cast
}
+define <4 x i24> @bitcast_constexpr_4i24_3i32_u1u() {
+; CHECK-LABEL: @bitcast_constexpr_4i24_3i32_u1u(
+; CHECK-NEXT: ret <4 x i24> <i24 undef, i24 256, i24 0, i24 undef>
+;
+ %cast = bitcast <3 x i32><i32 undef, i32 1, i32 undef> to <4 x i24>
+ ret <4 x i24> %cast
+}
+
+define <3 x i32> @bitcast_constexpr_3i32_4i24_n1255uu() {
+; CHECK-LABEL: @bitcast_constexpr_3i32_4i24_n1255uu(
+; CHECK-NEXT: ret <3 x i32> <i32 -1, i32 0, i32 undef>
+;
+ %cast = bitcast <4 x i24><i24 -1, i24 255, i24 undef, i24 undef> to <3 x i32>
+ ret <3 x i32> %cast
+}
+
define <1 x i32> @bitcast_constexpr_scalar_fp_to_vector_int() {
; CHECK-LABEL: @bitcast_constexpr_scalar_fp_to_vector_int(
; CHECK-NEXT: ret <1 x i32> splat (i32 1065353216)
diff --git a/llvm/test/Transforms/SCCP/bitcast-vector-refinement.l.ll b/llvm/test/Transforms/SCCP/bitcast-vector-refinement.l.ll
index 94df835e317b7..754f749128869 100644
--- a/llvm/test/Transforms/SCCP/bitcast-vector-refinement.l.ll
+++ b/llvm/test/Transforms/SCCP/bitcast-vector-refinement.l.ll
@@ -10,8 +10,7 @@ define <32 x i8> @test(i1 %cond) {
; CHECK-NEXT: [[TMP0:%.*]] = phi <4 x i64> [ zeroinitializer, %[[ENTRY]] ], [ splat (i64 1), %[[FOR_COND2]] ]
; CHECK-NEXT: br i1 [[COND]], label %[[FOR_COND2]], label %[[IF_THEN:.*]]
; CHECK: [[IF_THEN]]:
-; CHECK-NEXT: [[TMP1:%.*]] = bitcast <4 x i64> zeroinitializer to <32 x i8>
-; CHECK-NEXT: ret <32 x i8> [[TMP1]]
+; CHECK-NEXT: ret <32 x i8> zeroinitializer
;
entry:
br label %for.cond2
More information about the llvm-commits
mailing list