[llvm] [WebAssembly] Support f16x8.demote_f32x4_zero (PR #193564)
Brendan Dahl via llvm-commits
llvm-commits at lists.llvm.org
Wed Apr 22 11:50:26 PDT 2026
https://github.com/brendandahl created https://github.com/llvm/llvm-project/pull/193564
Add support for the f16x8.demote_f32x4_zero instruction. This instruction converts a v4f32 vector to a v4f16 and pads the result with zeros to fill the 128-bit register.
This enables efficient lowering of fptrunc operations from v4f32 to v4f16 when the result is zero-extended or when only the low lanes are needed. A DAG combine is included to recognize these patterns and fold them into the new instruction.
>From d1719e24f841c496bef622074b53a613f3ea0102 Mon Sep 17 00:00:00 2001
From: Brendan Dahl <brendan.dahl at gmail.com>
Date: Wed, 22 Apr 2026 18:13:39 +0000
Subject: [PATCH] [WebAssembly] Support f16x8.demote_f32x4_zero
Add support for the f16x8.demote_f32x4_zero instruction. This
instruction converts a v4f32 vector to a v4f16 and pads the result
with zeros to fill the 128-bit register.
This enables efficient lowering of fptrunc operations from v4f32 to
v4f16 when the result is zero-extended or when only the low lanes are
needed. A DAG combine is included to recognize these patterns and fold
them into the new instruction.
---
.../WebAssembly/WebAssemblyISelLowering.cpp | 58 ++++++++++++++-----
.../WebAssembly/WebAssemblyInstrSIMD.td | 1 +
.../CodeGen/WebAssembly/f16-intrinsics.ll | 19 ++++++
3 files changed, 62 insertions(+), 16 deletions(-)
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
index a3a873ebefb81..8f23d2a9b7cc6 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
@@ -245,8 +245,10 @@ WebAssemblyTargetLowering::WebAssemblyTargetLowering(
MVT::v2f64})
setOperationAction(ISD::BUILD_VECTOR, T, Custom);
- if (Subtarget->hasFP16())
+ if (Subtarget->hasFP16()) {
setOperationAction(ISD::BUILD_VECTOR, MVT::f16, Custom);
+ setOperationAction(ISD::FP_ROUND, MVT::v4f16, Custom);
+ }
// We have custom shuffle lowering to expose the shuffle mask
for (auto T : {MVT::v16i8, MVT::v8i16, MVT::v4i32, MVT::v4f32, MVT::v2i64,
@@ -1719,6 +1721,15 @@ void WebAssemblyTargetLowering::ReplaceNodeResults(
// Do not add any results, signifying that N should not be custom lowered.
// EXTEND_VECTOR_INREG is implemented for some vectors, but not all.
break;
+ case ISD::FP_ROUND: {
+ EVT VT = N->getValueType(0);
+ SDValue Src = N->getOperand(0);
+ if (VT == MVT::v4f16 && Src.getValueType() == MVT::v4f32) {
+ Results.push_back(
+ DAG.getNode(WebAssemblyISD::DEMOTE_ZERO, SDLoc(N), MVT::v8f16, Src));
+ }
+ break;
+ }
case ISD::ADD:
case ISD::SUB:
Results.push_back(Replace128Op(N, DAG));
@@ -3142,9 +3153,9 @@ performVectorTruncZeroCombine(SDNode *N, TargetLowering::DAGCombinerInfo &DCI) {
//
// Or this:
//
- // (concat_vectors (v2f32 (fp_round (v2f64 $x))), (v2f32 (splat 0)))
+ // (concat_vectors ({v2f32, v4f16} (fp_round ({v2f64, v4f32} $x))), ({v2f32, v4f16} (splat 0)))
//
- // into (f32x4.demote_zero_f64x2 $x).
+ // into ({f32x4, f16x8}.demote_zero_{f64x2, f32x4} $x).
EVT ResVT;
EVT ExpectedConversionType;
auto Conversion = N->getOperand(0);
@@ -3156,8 +3167,15 @@ performVectorTruncZeroCombine(SDNode *N, TargetLowering::DAGCombinerInfo &DCI) {
ExpectedConversionType = MVT::v2i32;
break;
case ISD::FP_ROUND:
- ResVT = MVT::v4f32;
- ExpectedConversionType = MVT::v2f32;
+ if (Conversion.getValueType() == MVT::v2f32) {
+ ResVT = MVT::v4f32;
+ ExpectedConversionType = MVT::v2f32;
+ } else if (Conversion.getValueType() == MVT::v4f16) {
+ ResVT = MVT::v8f16;
+ ExpectedConversionType = MVT::v4f16;
+ } else {
+ return SDValue();
+ }
break;
default:
return SDValue();
@@ -3170,7 +3188,9 @@ performVectorTruncZeroCombine(SDNode *N, TargetLowering::DAGCombinerInfo &DCI) {
return SDValue();
auto Source = Conversion.getOperand(0);
- if (Source.getValueType() != MVT::v2f64)
+ if (!((Source.getValueType() == MVT::v2f64 && ResVT == MVT::v4f32) ||
+ (Source.getValueType() == MVT::v2f64 && ResVT == MVT::v4i32) ||
+ (Source.getValueType() == MVT::v4f32 && ResVT == MVT::v8f16)))
return SDValue();
if (!IsZeroSplat(N->getOperand(1)) ||
@@ -3189,9 +3209,9 @@ performVectorTruncZeroCombine(SDNode *N, TargetLowering::DAGCombinerInfo &DCI) {
//
// Or this:
//
- // (v4f32 (fp_round (concat_vectors $x, (v2f64 (splat 0)))))
+ // ({v4f32, v8f16} (fp_round (concat_vectors $x, ({v2f64, v4f32} (splat 0)))))
//
- // into (f32x4.demote_zero_f64x2 $x).
+ // into ({f32x4, f16x8}.demote_zero_{f64x2, f32x4} $x).
EVT ResVT;
auto ConversionOp = N->getOpcode();
switch (ConversionOp) {
@@ -3200,7 +3220,7 @@ performVectorTruncZeroCombine(SDNode *N, TargetLowering::DAGCombinerInfo &DCI) {
ResVT = MVT::v4i32;
break;
case ISD::FP_ROUND:
- ResVT = MVT::v4f32;
+ ResVT = N->getValueType(0);
break;
default:
llvm_unreachable("unexpected op");
@@ -3210,19 +3230,25 @@ performVectorTruncZeroCombine(SDNode *N, TargetLowering::DAGCombinerInfo &DCI) {
return SDValue();
auto Concat = N->getOperand(0);
- if (Concat.getValueType() != MVT::v4f64)
+ if (Concat.getOpcode() != ISD::CONCAT_VECTORS)
return SDValue();
+ EVT ConcatVT = Concat.getValueType();
+ EVT SourceVT = Concat.getOperand(0).getValueType();
- auto Source = Concat.getOperand(0);
- if (Source.getValueType() != MVT::v2f64)
+ if (!IsZeroSplat(Concat.getOperand(1)))
return SDValue();
- if (!IsZeroSplat(Concat.getOperand(1)) ||
- Concat.getOperand(1).getValueType() != MVT::v2f64)
- return SDValue();
+ if (ConversionOp == ISD::FP_ROUND) {
+ if (!((ConcatVT == MVT::v4f64 && SourceVT == MVT::v2f64 && ResVT == MVT::v4f32) ||
+ (ConcatVT == MVT::v8f32 && SourceVT == MVT::v4f32 && ResVT == MVT::v8f16)))
+ return SDValue();
+ } else {
+ if (ConcatVT != MVT::v4f64 || SourceVT != MVT::v2f64 || ResVT != MVT::v4i32)
+ return SDValue();
+ }
unsigned Op = GetWasmConversionOp(ConversionOp);
- return DAG.getNode(Op, SDLoc(N), ResVT, Source);
+ return DAG.getNode(Op, SDLoc(N), ResVT, Concat.getOperand(0));
}
// Helper to extract VectorWidth bits from Vec, starting from IdxVal.
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrSIMD.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrSIMD.td
index 19f2b46b13037..1ec6774bfa81a 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrSIMD.td
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrSIMD.td
@@ -1654,6 +1654,7 @@ def demote_t : SDTypeProfile<1, 1, [SDTCisVec<0>, SDTCisVec<1>]>;
def demote_zero : SDNode<"WebAssemblyISD::DEMOTE_ZERO", demote_t>;
defm "" : SIMDConvert<F32x4, F64x2, demote_zero,
"demote_f64x2_zero", 0x5e>;
+defm "" : HalfPrecisionConvert<F16x8, F32x4, demote_zero, "demote_f32x4_zero", 0x14c>;
def promote_t : SDTypeProfile<1, 1, [SDTCisVec<0>, SDTCisVec<1>]>;
def promote_low : SDNode<"WebAssemblyISD::PROMOTE_LOW", promote_t>;
diff --git a/llvm/test/CodeGen/WebAssembly/f16-intrinsics.ll b/llvm/test/CodeGen/WebAssembly/f16-intrinsics.ll
index bb28f462ebd0f..1eb1ca707a304 100644
--- a/llvm/test/CodeGen/WebAssembly/f16-intrinsics.ll
+++ b/llvm/test/CodeGen/WebAssembly/f16-intrinsics.ll
@@ -420,3 +420,22 @@ define <4 x float> @promote_low_v4f32_2(<8 x half> %x) {
%a = shufflevector <8 x float> %v, <8 x float> poison, <4 x i32> <i32 0, i32 1, i32 2, i32 3>
ret <4 x float> %a
}
+
+define <8 x half> @demote_v4f32(<4 x float> %x) {
+; CHECK-LABEL: demote_v4f32:
+; CHECK: .functype demote_v4f32 (v128) -> (v128)
+; CHECK-NEXT: f16x8.demote_f32x4_zero $push0=, $0
+; CHECK-NEXT: return $pop0
+ %v = fptrunc <4 x float> %x to <4 x half>
+ %a = shufflevector <4 x half> %v, <4 x half> zeroinitializer, <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7>
+ ret <8 x half> %a
+}
+
+define <4 x half> @demote_v4f32_no_padding(<4 x float> %x) {
+; CHECK-LABEL: demote_v4f32_no_padding:
+; CHECK: .functype demote_v4f32_no_padding (v128) -> (v128)
+; CHECK-NEXT: f16x8.demote_f32x4_zero $push[[R:[0-9]+]]=, $0
+; CHECK-NEXT: return $pop[[R]]
+ %v = fptrunc <4 x float> %x to <4 x half>
+ ret <4 x half> %v
+}
More information about the llvm-commits
mailing list