[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