[PATCH] x86: Expand the default truncsfbf2 like vcvtneps2bf16

"H.J. Lu" <[email protected]>
Newsgroups gmane.comp.gcc.patches
Message-ID <CAMe9rOo_V1Ss_F3S_APb4R5oYxbdp_vfA=kJYDBgU-so1Wou0A@mail.gmail.com>
Expand the default truncsfbf2 like vcvtneps2bf16, which doesn't honor
SNAN, turns sNAN into qNAN quietly, it always rounds to nearest even
and flushes denormals to zero, with

(fromi + 0x7fff + ((fromi >> 16) & 1)) >> 16

and flush denormals to zero.

gcc/

PR target/126933
* config/i386/i386-expand.cc (ix86_expand_truncsfbf2): New.
* config/i386/i386-protos.h (ix86_expand_truncsfbf2): Likewise.
* config/i386/i386.md (truncsfbf2): Changed to define_expand.
(truncsfbf2_vcvtneps2bf16): New.

gcc/testsuite/

PR target/126933
* gcc.target/i386/truncsfbf-1.c (dg-options): Add
-mno-avxneconvert -mno-avx512bf16 -fno-asynchronous-unwind-tables.
Use check-function-bodies to check updated codegen.
* gcc.target/i386/truncsfbf-2.c (dg-options): Add
-mno-avxneconvert -mno-avx512bf16.
(foo): Make it static with __attribute__ ((noipa, noinline)).
(CALC): Add __attribute__ ((noipa, noinline)).  Flush denormal
to zero.
* gcc.target/i386/truncsfbf-3.c: New test.
* gcc.target/i386/truncsfbf-4.c: Likewise.
* gcc.target/i386/truncsfbf-5.c: Likewise.
* gcc.target/i386/truncsfbf-6.c: Likewise.

-- 
H.J.
0001-x86-Expand-the-default-truncsfbf2-like-vcvtneps2bf16.patch (text/x-patch, 18.2 KB)
From c5ac1c32893ee8bcc972e63c9f0325917cd79c47 Mon Sep 17 00:00:00 2001
From: "H.J. Lu" <[email protected]>
Date: Wed, 19 Aug 2026 11:42:38 +0800
Subject: [PATCH] x86: Expand the default truncsfbf2 like vcvtneps2bf16

Expand the default truncsfbf2 like vcvtneps2bf16, which doesn't honor
SNAN, turns sNAN into qNAN quietly, it always rounds to nearest even
and flushes denormals to zero, with

(fromi + 0x7fff + ((fromi >> 16) & 1)) >> 16

and flush denormals to zero.

gcc/

	PR target/126933
	* config/i386/i386-expand.cc (ix86_expand_truncsfbf2): New.
	* config/i386/i386-protos.h (ix86_expand_truncsfbf2): Likewise.
	* config/i386/i386.md (truncsfbf2): Changed to define_expand.
	(truncsfbf2_vcvtneps2bf16): New.

gcc/testsuite/

	PR target/126933
	* gcc.target/i386/truncsfbf-1.c (dg-options): Add
	-mno-avxneconvert -mno-avx512bf16 -fno-asynchronous-unwind-tables.
	Use check-function-bodies to check updated codegen.
	* gcc.target/i386/truncsfbf-2.c (dg-options): Add
	-mno-avxneconvert -mno-avx512bf16.
	(foo): Make it static with __attribute__ ((noipa, noinline)).
	(CALC): Add __attribute__ ((noipa, noinline)).  Flush denormal
	to zero.
	* gcc.target/i386/truncsfbf-3.c: New test.
	* gcc.target/i386/truncsfbf-4.c: Likewise.
	* gcc.target/i386/truncsfbf-5.c: Likewise.
	* gcc.target/i386/truncsfbf-6.c: Likewise.

Signed-off-by: H.J. Lu <[email protected]>
---
 gcc/config/i386/i386-expand.cc              | 90 ++++++++++++++++++++
 gcc/config/i386/i386-protos.h               |  1 +
 gcc/config/i386/i386.md                     | 34 ++++----
 gcc/testsuite/gcc.target/i386/truncsfbf-1.c | 43 +++++++++-
 gcc/testsuite/gcc.target/i386/truncsfbf-2.c | 27 +++++-
 gcc/testsuite/gcc.target/i386/truncsfbf-3.c | 26 ++++++
 gcc/testsuite/gcc.target/i386/truncsfbf-4.c | 26 ++++++
 gcc/testsuite/gcc.target/i386/truncsfbf-5.c | 92 ++++++++++++++++++++
 gcc/testsuite/gcc.target/i386/truncsfbf-6.c | 93 +++++++++++++++++++++
 9 files changed, 411 insertions(+), 21 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/i386/truncsfbf-3.c
 create mode 100644 gcc/testsuite/gcc.target/i386/truncsfbf-4.c
 create mode 100644 gcc/testsuite/gcc.target/i386/truncsfbf-5.c
 create mode 100644 gcc/testsuite/gcc.target/i386/truncsfbf-6.c

diff --git a/gcc/config/i386/i386-expand.cc b/gcc/config/i386/i386-expand.cc
index 34c9599f928..83fa6e33358 100644
--- a/gcc/config/i386/i386-expand.cc
+++ b/gcc/config/i386/i386-expand.cc
@@ -22554,6 +22554,96 @@ ix86_expand_truncdf_32 (rtx operand0, rtx operand1)
   emit_move_insn (operand0, res);
 }
 
+/* Expand truncsfbf2 like vcvtneps2bf16, which doesn't honor SNAN,
+   turns sNAN into qNAN quietly, it always rounds to nearest even
+   and flushes denormals to zero.  We can expand the conversion
+   inline as (fromi + 0x7fff + ((fromi >> 16) & 1)) >> 16, flushing
+   denormals to zero.  */
+
+void
+ix86_expand_truncsfbf2 (rtx op0, rtx op1)
+{
+  rtx set;
+
+  if (TARGET_AVXNECONVERT
+      || (TARGET_AVX512BF16 && TARGET_AVX512VL))
+    {
+      set = gen_truncsfbf2_vcvtneps2bf16 (op0, op1);
+      emit_insn (set);
+      return;
+    }
+
+  /* Convert OP1 to REG1_SI in SImode.  */
+  rtx reg1_si = gen_reg_rtx (SImode);
+  rtx op1_si = gen_lowpart (SImode, op1);
+  set = gen_rtx_SET (reg1_si, op1_si);
+  emit_insn (set);
+
+  /* Set TMP0 to REG1_SI >> 16. */
+  rtx tmp0 = expand_simple_binop (SImode, LSHIFTRT, reg1_si,
+				  GEN_INT(16), nullptr, 0,
+				  OPTAB_DIRECT);
+  /* Set TMP1 to TMP0 & 1. */
+  rtx tmp1 = expand_simple_binop (SImode, AND, tmp0, const1_rtx,
+				  nullptr, 0, OPTAB_DIRECT);
+
+  /* Set TMP2 to REG1_SI + 0x7fff.  */
+  rtx tmp2 = expand_simple_binop (SImode, PLUS, reg1_si,
+				  GEN_INT (0x7fff), nullptr, 0,
+				  OPTAB_DIRECT);
+  /* Set TMP1 to TMP2 + TMP1.  */
+  tmp1 = expand_simple_binop (SImode, PLUS, tmp2, tmp1, nullptr, 0,
+			      OPTAB_DIRECT);
+
+  /* Set TMP1 to TMP1 >> 16. */
+  tmp1 = expand_simple_binop (SImode, LSHIFTRT, tmp1, GEN_INT(16),
+			      nullptr, 0, OPTAB_DIRECT);
+
+  rtx_code_label *zero_label = gen_label_rtx ();
+  rtx_code_label *done_label = gen_label_rtx ();
+
+  /* TMP1 is zero or denormal if (TMP1 & 0x7f80) == 0.  */
+  tmp0 = expand_simple_binop (SImode, AND, tmp1, GEN_INT(0x7f80),
+			      nullptr, 0, OPTAB_DIRECT);
+
+  emit_cmp_and_jump_insns (tmp0, const0_rtx, EQ, nullptr, SImode,
+			   true, zero_label);
+
+  /* Cast TMP1 to TMP2 in HImode.  */
+  tmp2 = gen_reg_rtx (HImode);
+  tmp0 = gen_lowpart (HImode, tmp1);
+  set = gen_rtx_SET (tmp2, tmp0);
+  emit_insn (set);
+
+  /* Convert TMP2 to OP0.  */
+  tmp0 = gen_lowpart (BFmode, tmp2);
+  set = gen_rtx_SET (op0, tmp0);
+  emit_insn (set);
+
+  emit_jump_insn (gen_jump (done_label));
+  emit_barrier ();
+
+  emit_label (zero_label);
+
+  /* Flush TMP1 to zero while keeping the sign bit for zero and
+     denormal.  */
+  tmp1 = expand_simple_binop (SImode, AND, tmp1, GEN_INT(0x8000),
+			      nullptr, 0, OPTAB_DIRECT);
+
+  /* Cast TMP1 to TMP2 in HImode.  */
+  tmp2 = gen_reg_rtx (HImode);
+  tmp0 = gen_lowpart (HImode, tmp1);
+  set = gen_rtx_SET (tmp2, tmp0);
+  emit_insn (set);
+
+  /* Convert TMP2 to OP0.  */
+  tmp0 = gen_lowpart (BFmode, tmp2);
+  set = gen_rtx_SET (op0, tmp0);
+  emit_insn (set);
+
+  emit_label (done_label);
+}
+
 /* Expand SSE sequence for computing round
    from OPERAND1 storing into OPERAND0.  */
 void
diff --git a/gcc/config/i386/i386-protos.h b/gcc/config/i386/i386-protos.h
index 21994d3d498..2687de646b0 100644
--- a/gcc/config/i386/i386-protos.h
+++ b/gcc/config/i386/i386-protos.h
@@ -233,6 +233,7 @@ extern void ix86_expand_floorceil (rtx, rtx, bool);
 extern void ix86_expand_floorceildf_32 (rtx, rtx, bool);
 extern void ix86_expand_trunc (rtx, rtx);
 extern void ix86_expand_truncdf_32 (rtx, rtx);
+extern void ix86_expand_truncsfbf2 (rtx, rtx);
 extern void ix86_expand_round (rtx, rtx);
 extern void ix86_expand_rounddf_32 (rtx, rtx);
 extern void ix86_expand_round_sse4 (rtx, rtx);
diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md
index 4e18797743f..9230c599e86 100644
--- a/gcc/config/i386/i386.md
+++ b/gcc/config/i386/i386.md
@@ -5817,28 +5817,32 @@ (define_insn "*trunc<mode>hf2"
    (set_attr "prefix" "evex")
    (set_attr "mode" "HF")])
 
-/* vcvtneps2bf16 doesn't honor SNAN, and turn sNAN into qNAN quietly,
-   and it always round to even.
-   flag_unsafe_math_optimization is needed for psrld.
-   If we don't expect qNaNs nor sNaNs and can assume rounding
-   to nearest, we can expand the conversion inline as
-   (fromi + 0x7fff + ((fromi >> 16) & 1)) >> 16.  */
-(define_insn "truncsfbf2"
-  [(set (match_operand:BF 0 "register_operand" "=x,x,v,Yv")
+(define_expand "truncsfbf2"
+  [(set (match_operand:BF 0 "register_operand" "")
 	(float_truncate:BF
-	  (match_operand:SF 1 "register_operand" "0,x,v,Yv")))]
+	  (match_operand:SF 1 "register_operand" "")))]
   "TARGET_SSE2 && !HONOR_NANS (BFmode) && !flag_rounding_math
    && (flag_unsafe_math_optimizations
        || TARGET_AVXNECONVERT
        || (TARGET_AVX512BF16 && TARGET_AVX512VL))"
+{
+  ix86_expand_truncsfbf2 (operands[0], operands[1]);
+  DONE;
+})
+
+(define_insn "truncsfbf2_vcvtneps2bf16"
+  [(set (match_operand:BF 0 "register_operand" "=x,v")
+	(float_truncate:BF
+	  (match_operand:SF 1 "register_operand" "x,v")))]
+  "(TARGET_AVXNECONVERT || (TARGET_AVX512BF16 && TARGET_AVX512VL))
+   && !HONOR_NANS (BFmode)
+   && !flag_rounding_math"
   "@
-  psrld\t{$16, %0|%0, 16}
   %{vex%} vcvtneps2bf16\t{%1, %0|%0, %1}
-  vcvtneps2bf16\t{%1, %0|%0, %1}
-  vpsrld\t{$16, %1, %0|%0, %1, 16}"
-  [(set_attr "isa" "noavx,avxneconvert,avx512bf16vl,avx")
-   (set_attr "prefix" "orig,vex,evex,vex")
-   (set_attr "type" "sseishft1,ssecvt,ssecvt,sseishft1")])
+  vcvtneps2bf16\t{%1, %0|%0, %1}"
+  [(set_attr "isa" "avxneconvert,avx512bf16vl")
+   (set_attr "prefix" "vex,evex")
+   (set_attr "type" "ssecvt,ssecvt")])
 
 ;; Signed conversion to DImode.
 
diff --git a/gcc/testsuite/gcc.target/i386/truncsfbf-1.c b/gcc/testsuite/gcc.target/i386/truncsfbf-1.c
index dd3ff8a50b4..6102bd634f1 100644
--- a/gcc/testsuite/gcc.target/i386/truncsfbf-1.c
+++ b/gcc/testsuite/gcc.target/i386/truncsfbf-1.c
@@ -1,6 +1,45 @@
 /* { dg-do compile } */
-/* { dg-options "-msse2 -O2 -ffast-math" } */
-/* { dg-final { scan-assembler-times "psrld" 1 } } */
+/* { dg-options "-msse2 -O2 -ffast-math -mno-avxneconvert -mno-avx512bf16 -fno-asynchronous-unwind-tables" } */
+/* Keep labels and directives ('.cfi_startproc', '.cfi_endproc').  */
+/* { dg-final { check-function-bodies "ia32" "*ia32" "" { target ia32 } {^\t?\.} } } */
+/* { dg-final { check-function-bodies "x64" "*x64" "" { target { ! ia32 } } {^\t?\.} } } */
+
+/*
+ia32foo:
+ia32	movl	4\(%esp\), %edx
+ia32	movl	%edx, %eax
+ia32	shrl	\$16, %eax
+ia32	andl	\$1, %eax
+ia32	leal	32767\(%edx,%eax\), %eax
+ia32	shrl	\$16, %eax
+ia32	movl	%eax, %ecx
+ia32	andw	\$-32768, %cx
+ia32	movl	%ecx, %edx
+ia32	testl	\$32640, %eax
+ia32	je	.L[0-9]+
+ia32	movl	%eax, %edx
+ia32.L[0-9]+:
+ia32	(v|)movd	%edx, %xmm0
+ia32	ret
+ia32...
+*ia32
+
+x64foo:
+x64	(v|)movd	%xmm0, %eax
+x64	movl	%eax, %edx
+x64	shrl	\$16, %edx
+x64	andl	\$1, %edx
+x64	leal	32767\(%(e|r)ax,%(e|r)dx\), %eax
+x64	shrl	\$16, %eax
+x64	testl	\$32640, %eax
+x64	jne	.L[0-9]+
+x64	andw	\$-32768, %ax
+x64.L[0-9]+:
+x64	(v|)movd	%eax, %xmm0
+x64	ret
+x64...
+*x64
+*/
 
 __bf16
 foo (float a)
diff --git a/gcc/testsuite/gcc.target/i386/truncsfbf-2.c b/gcc/testsuite/gcc.target/i386/truncsfbf-2.c
index f4952f88fc9..36b311a16e5 100644
--- a/gcc/testsuite/gcc.target/i386/truncsfbf-2.c
+++ b/gcc/testsuite/gcc.target/i386/truncsfbf-2.c
@@ -1,23 +1,31 @@
 /* { dg-do run } */
-/* { dg-options "-msse2 -O2 -ffast-math" } */
+/* { dg-options "-msse2 -O2 -ffast-math -mno-avxneconvert -mno-avx512bf16" } */
 
+#include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
 #include <string.h>
 #include <math.h>
 
-__bf16
+__attribute__ ((noipa, noinline))
+static __bf16
 foo (float a)
 {
   return a;
 }
 
+__attribute__ ((noipa, noinline))
 static __bf16
 CALC (float *a)
 {
   uint32_t bits;
   memcpy (&bits, a, sizeof (bits));
+  uint32_t rounding_bias = 0x7FFF + ((bits >> 16) & 1);
+  bits += rounding_bias;
   bits >>= 16;
+  /* Flush denormal to zero like vcvtneps2bf16.  */
+  if ((bits & 0x7f80) == 0)
+    bits &= 0x8000;
   uint16_t bfloat16_bits = (uint16_t) bits;
   __bf16 bf16;
   memcpy (&bf16, &bfloat16_bits, sizeof (bf16));
@@ -31,6 +39,7 @@ main (void)
                           3.1415926f, -3.1415926f, 1e-8f, -1e-8f,
                           1.0e+38f, -1.0e+38f, 1.0e-38f, -1.0e-38f };
   size_t num_values = sizeof (test_values) / sizeof (test_values[0]);
+  bool failed = false;
 
   for (size_t i = 0; i < num_values; ++i)
     {
@@ -40,7 +49,10 @@ main (void)
 
       /* Verify psrld $16, %0 == %0 >> 16 */
       if (memcmp (&hw_bf16, &sw_bf16, sizeof (__bf16)) != 0)
-        abort ();
+	{
+	  printf ("float -> bf16 failed: %d, %e\n", i, original);
+	  failed = true;
+	}
 
       /* Reconstruct the float value from the __bf16 bits */
       uint16_t bf16_bits;
@@ -59,7 +71,14 @@ main (void)
                             ? ldexpf (1.0f, -126 - 7)
                             : ldexpf (1.0f, exponent - 7);
       if (diff > expected_loss)
-        abort ();
+	{
+	  printf ("bf16 -> float failed: %d, %e\n", i, original);
+	  failed = true;
+	}
     }
+
+  if (failed)
+    abort ();
+
   return 0;
 }
diff --git a/gcc/testsuite/gcc.target/i386/truncsfbf-3.c b/gcc/testsuite/gcc.target/i386/truncsfbf-3.c
new file mode 100644
index 00000000000..afabacbacb6
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/truncsfbf-3.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-options "-msse2 -O2 -ffast-math -mavxneconvert -mno-avx512bf16 -fno-asynchronous-unwind-tables" } */
+/* Keep labels and directives ('.cfi_startproc', '.cfi_endproc').  */
+/* { dg-final { check-function-bodies "ia32" "*ia32" "" { target ia32 } {^\t?\.} } } */
+/* { dg-final { check-function-bodies "x64" "*x64" "" { target { ! ia32 } } {^\t?\.} } } */
+
+/*
+ia32foo:
+ia32	vmovss	4\(%esp\), %xmm0
+ia32	{vex} vcvtneps2bf16	%xmm0, %xmm0
+ia32	ret
+ia32...
+*ia32
+
+x64foo:
+x64	{vex} vcvtneps2bf16	%xmm0, %xmm0
+x64	ret
+x64...
+*x64
+*/
+
+__bf16
+foo (float a)
+{
+  return a;
+}
diff --git a/gcc/testsuite/gcc.target/i386/truncsfbf-4.c b/gcc/testsuite/gcc.target/i386/truncsfbf-4.c
new file mode 100644
index 00000000000..ea2653f3e90
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/truncsfbf-4.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-options "-msse2 -O2 -ffast-math -mno-avxneconvert -mavx512vl -mavx512bf16 -fno-asynchronous-unwind-tables" } */
+/* Keep labels and directives ('.cfi_startproc', '.cfi_endproc').  */
+/* { dg-final { check-function-bodies "ia32" "*ia32" "" { target ia32 } {^\t?\.} } } */
+/* { dg-final { check-function-bodies "x64" "*x64" "" { target { ! ia32 } } {^\t?\.} } } */
+
+/*
+ia32foo:
+ia32	vmovss	4\(%esp\), %xmm0
+ia32	vcvtneps2bf16	%xmm0, %xmm0
+ia32	ret
+ia32...
+*ia32
+
+x64foo:
+x64	vcvtneps2bf16	%xmm0, %xmm0
+x64	ret
+x64...
+*x64
+*/
+
+__bf16
+foo (float a)
+{
+  return a;
+}
diff --git a/gcc/testsuite/gcc.target/i386/truncsfbf-5.c b/gcc/testsuite/gcc.target/i386/truncsfbf-5.c
new file mode 100644
index 00000000000..cd59a997bea
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/truncsfbf-5.c
@@ -0,0 +1,92 @@
+/* { dg-do run } */
+/* { dg-options "-O2 -ffast-math -march=x86-64" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <math.h>
+
+__attribute__ ((noipa, noinline, target("avxneconvert")))
+static __bf16
+foo (float a)
+{
+  return a;
+}
+
+__attribute__ ((noipa, noinline))
+static __bf16
+CALC (float *a)
+{
+  uint32_t bits;
+  memcpy (&bits, a, sizeof (bits));
+  uint32_t rounding_bias = 0x7FFF + ((bits >> 16) & 1);
+  bits += rounding_bias;
+  bits >>= 16;
+  /* Flush denormal to zero like vcvtneps2bf16.  */
+  if ((bits & 0x7f80) == 0)
+    bits &= 0x8000;
+  uint16_t bfloat16_bits = (uint16_t) bits;
+  __bf16 bf16;
+  memcpy (&bf16, &bfloat16_bits, sizeof (bf16));
+  return bf16;
+}
+
+__attribute__ ((noipa, noinline))
+static void
+do_test (void)
+{
+  float test_values[] = { 0.0f, -0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1000.0f, -1000.0f,
+                          3.1415926f, -3.1415926f, 1e-8f, -1e-8f,
+                          1.0e+38f, -1.0e+38f, 1.0e-38f, -1.0e-38f };
+  size_t num_values = sizeof (test_values) / sizeof (test_values[0]);
+  bool failed = false;
+
+  for (size_t i = 0; i < num_values; ++i)
+    {
+      float original = test_values[i];
+      __bf16 hw_bf16 = foo (original);
+      __bf16 sw_bf16 = CALC (&original);
+
+      /* Verify psrld $16, %0 == %0 >> 16 */
+      if (memcmp (&hw_bf16, &sw_bf16, sizeof (__bf16)) != 0)
+	{
+	  printf ("float -> bf16 failed: %d, %e\n", i, original);
+	  failed = true;
+	}
+
+      /* Reconstruct the float value from the __bf16 bits */
+      uint16_t bf16_bits;
+      memcpy (&bf16_bits, &hw_bf16, sizeof (bf16_bits));
+      uint32_t reconstructed_bits = ((uint32_t) bf16_bits) << 16;
+      float converted;
+      memcpy (&converted, &reconstructed_bits, sizeof (converted));
+
+      float diff = fabsf (original - converted);
+
+      /* Expected Maximum Precision Loss */
+      uint32_t orig_bits;
+      memcpy (&orig_bits, &original, sizeof (orig_bits));
+      int exponent = ((orig_bits >> 23) & 0xFF) - 127;
+      float expected_loss = (exponent == -127)
+                            ? ldexpf (1.0f, -126 - 7)
+                            : ldexpf (1.0f, exponent - 7);
+      if (diff > expected_loss)
+	{
+	  printf ("bf16 -> float failed: %d, %e\n", i, original);
+	  failed = true;
+	}
+    }
+
+  if (failed)
+    abort ();
+}
+
+int
+main (void)
+{
+  if (__builtin_cpu_supports ("avxneconvert"))
+    do_test ();
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/i386/truncsfbf-6.c b/gcc/testsuite/gcc.target/i386/truncsfbf-6.c
new file mode 100644
index 00000000000..ed53d48b0e2
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/truncsfbf-6.c
@@ -0,0 +1,93 @@
+/* { dg-do run } */
+/* { dg-options "-O2 -ffast-math -march=x86-64" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <math.h>
+
+__attribute__ ((noipa, noinline, target("avx512vl,avx512bf16")))
+static __bf16
+foo (float a)
+{
+  return a;
+}
+
+__attribute__ ((noipa, noinline))
+static __bf16
+CALC (float *a)
+{
+  uint32_t bits;
+  memcpy (&bits, a, sizeof (bits));
+  uint32_t rounding_bias = 0x7FFF + ((bits >> 16) & 1);
+  bits += rounding_bias;
+  bits >>= 16;
+  /* Flush denormal to zero like vcvtneps2bf16.  */
+  if ((bits & 0x7f80) == 0)
+    bits &= 0x8000;
+  uint16_t bfloat16_bits = (uint16_t) bits;
+  __bf16 bf16;
+  memcpy (&bf16, &bfloat16_bits, sizeof (bf16));
+  return bf16;
+}
+
+__attribute__ ((noipa, noinline))
+static void
+do_test (void)
+{
+  float test_values[] = { 0.0f, -0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1000.0f, -1000.0f,
+                          3.1415926f, -3.1415926f, 1e-8f, -1e-8f,
+                          1.0e+38f, -1.0e+38f, 1.0e-38f, -1.0e-38f };
+  size_t num_values = sizeof (test_values) / sizeof (test_values[0]);
+  bool failed = false;
+
+  for (size_t i = 0; i < num_values; ++i)
+    {
+      float original = test_values[i];
+      __bf16 hw_bf16 = foo (original);
+      __bf16 sw_bf16 = CALC (&original);
+
+      /* Verify psrld $16, %0 == %0 >> 16 */
+      if (memcmp (&hw_bf16, &sw_bf16, sizeof (__bf16)) != 0)
+	{
+	  printf ("float -> bf16 failed: %d, %e\n", i, original);
+	  failed = true;
+	}
+
+      /* Reconstruct the float value from the __bf16 bits */
+      uint16_t bf16_bits;
+      memcpy (&bf16_bits, &hw_bf16, sizeof (bf16_bits));
+      uint32_t reconstructed_bits = ((uint32_t) bf16_bits) << 16;
+      float converted;
+      memcpy (&converted, &reconstructed_bits, sizeof (converted));
+
+      float diff = fabsf (original - converted);
+
+      /* Expected Maximum Precision Loss */
+      uint32_t orig_bits;
+      memcpy (&orig_bits, &original, sizeof (orig_bits));
+      int exponent = ((orig_bits >> 23) & 0xFF) - 127;
+      float expected_loss = (exponent == -127)
+                            ? ldexpf (1.0f, -126 - 7)
+                            : ldexpf (1.0f, exponent - 7);
+      if (diff > expected_loss)
+	{
+	  printf ("bf16 -> float failed: %d, %e\n", i, original);
+	  failed = true;
+	}
+    }
+
+  if (failed)
+    abort ();
+}
+
+int
+main (void)
+{
+  if (__builtin_cpu_supports ("avx512vl")
+      && __builtin_cpu_supports ("avx512bf16"))
+    do_test ();
+
+  return 0;
+}
-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.