On Mon, Aug 24, 2026 at 3:44 PM Liu, Hongtao <[email protected]> wrote:
>
> With this patch the float -> __bf16 conversion still gives a different
> result with and without AVX512BF16 for the same float input:
>
> /* { dg-do run } */
> /* { dg-options "-O2 -ffast-math -march=x86-64" } */
>
> #include <stdio.h>
> #include <stdint.h>
> #include <string.h>
>
> /* Both functions are just "return a;".  FOO is compiled for plain
>    x86-64, so it uses the inline expansion; BAR uses vcvtneps2bf16.  */
>
> __attribute__ ((noipa, noinline))
> static __bf16
> foo (float a)
> {
>   return a;
> }
>
> __attribute__ ((noipa, noinline, target ("avx512vl,avx512bf16")))
> static __bf16
> bar (float a)
> {
>   return a;
> }
>
> static uint16_t
> bits16 (__bf16 x)
> {
>   uint16_t r;
>   memcpy (&r, &x, sizeof (r));
>   return r;
> }
>
> int
> main (void)
> {
>   uint32_t v[] = { 0x007f8000, 0x807f8000, 0x007fffff, 0x807fffff,
>                    0x007f7fff, 0x00367000, 0x00800000 };
>
>   for (unsigned i = 0; i < sizeof v / sizeof v[0]; i++)
>     {
>       float f;
>       memcpy (&f, &v[i], sizeof (f));
>       uint16_t a = bits16 (foo (f));
>       uint16_t b = bits16 (bar (f));
>       printf ("0x%08x  foo=0x%04x  bar=0x%04x  %s\n",
>               v[i], a, b, a == b ? "same" : "DIFFER");
>     }
>   return 0;
> }
>
> On AVX512BF16 hardware this prints:
>
> 0x007f8000  foo=0x0080  bar=0x0000  DIFFER
> 0x807f8000  foo=0x8080  bar=0x8000  DIFFER
> 0x007fffff  foo=0x0080  bar=0x0000  DIFFER
> 0x807fffff  foo=0x8080  bar=0x8000  DIFFER
> 0x007f7fff  foo=0x0000  bar=0x0000  same
> 0x00367000  foo=0x0000  bar=0x0000  same
> 0x00800000  foo=0x0080  bar=0x0080  same
>
> These are SFmode denormals: vcvtneps2bf16 returns a sign preserving zero,
> the inline expansion returns +-0x0080, i.e. +-2^-126.  Sweeping the whole
> SFmode denormal range, 65536 inputs differ, the 32768 mantissas in
> [0x7f8000, 0x7fffff] for each sign.
>
> The reason is that the flush test is applied to the rounded result rather
> than to the input:
>
> > +  /* TMP1 is zero or denormal if (TMP1 & 0x7f80) == 0.  */
> > +  tmp0 = expand_simple_binop (SImode, AND, tmp1, GEN_INT(0x7f80),
> > +                              nullptr, 0, OPTAB_DIRECT);
>
> Testing REG1_SI & 0x7f800000 instead makes the two agree for all 2^32
> non-NaN inputs.
>

Fixed in the v3 patch with tests.

Changes in v3:

1. Test (REG1_SI & 0x7f800000) == 0 for input denormals.
2. Add tests for input denormals.
3. Update expected codegen in truncsfbf-1.c.

> Changes in v2:
>
> 1. Remove duplicated codes in ix86_expand_truncsfbf2
> 2. Scan cmov, instead of branch, in gcc.target/i386/truncsfbf-1.c.
>

Thanks.

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

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

and flush input 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
and update comments.
(truncsfbf2_vcvtneps2bf16): New.

gcc/testsuite/

PR target/126933
* gcc.target/i386/truncsfbf-1.c (dg-options): Add
-mno-avxneconvert -mno-avx512bf16.  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 input
denormal to zero.
(main): Add tests for float denormal inputs.
* 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.
From 165cd3d31f977505c6d51133b9583c1b5418a11e Mon Sep 17 00:00:00 2001
From: "H.J. Lu" <[email protected]>
Date: Wed, 19 Aug 2026 11:42:38 +0800
Subject: [PATCH v3] x86: Expand the default truncsfbf2 like vcvtneps2bf16

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

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

and flush input 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
	and update comments.
	(truncsfbf2_vcvtneps2bf16): New.

gcc/testsuite/

	PR target/126933
	* gcc.target/i386/truncsfbf-1.c (dg-options): Add
	-mno-avxneconvert -mno-avx512bf16.  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 input
	denormal to zero.
	(main): Add tests for float denormal inputs.
	* 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              | 89 ++++++++++++++++++++
 gcc/config/i386/i386-protos.h               |  1 +
 gcc/config/i386/i386.md                     | 38 +++++----
 gcc/testsuite/gcc.target/i386/truncsfbf-1.c | 44 +++++++++-
 gcc/testsuite/gcc.target/i386/truncsfbf-2.c | 47 +++++++++--
 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, 430 insertions(+), 26 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..b44a87bd92f 100644
--- a/gcc/config/i386/i386-expand.cc
+++ b/gcc/config/i386/i386-expand.cc
@@ -22554,6 +22554,95 @@ 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);
+
+  rtx_code_label *zero_label = gen_label_rtx ();
+  rtx_code_label *cast_label = gen_label_rtx ();
+
+  rtx result = gen_reg_rtx (SImode);
+
+  /* OP1 is zero or denormal if (REG1_SI & 0x7f800000) == 0.  */
+  rtx tmp1 = expand_simple_binop (SImode, AND, reg1_si,
+				  GEN_INT(0x7f800000), nullptr, 0,
+				  OPTAB_DIRECT);
+
+  emit_cmp_and_jump_insns (tmp1, const0_rtx, EQ, nullptr, SImode,
+			   true, zero_label);
+
+  /* Set TMP1 to TMP0 & 1. */
+  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);
+
+  /* Move TMP1 to RESULT.  */
+  emit_move_insn (result, tmp1);
+
+  emit_jump_insn (gen_jump (cast_label));
+  emit_barrier ();
+
+  emit_label (zero_label);
+
+  /* Flush TMP0 to zero while keeping the sign bit for zero and
+     denormal.  */
+  tmp1 = expand_simple_binop (SImode, AND, tmp0, GEN_INT(0x8000),
+			      nullptr, 0, OPTAB_DIRECT);
+
+  /* Move TMP1 to RESULT.  */
+  emit_move_insn (result, tmp1);
+
+  emit_label (cast_label);
+
+  /* Cast RESULT to TMP2 in HImode.  */
+  tmp2 = gen_reg_rtx (HImode);
+  tmp0 = gen_lowpart (HImode, result);
+  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);
+}
+
 /* 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..a42a8cbcf13 100644
--- a/gcc/config/i386/i386.md
+++ b/gcc/config/i386/i386.md
@@ -5817,28 +5817,36 @@ (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")
+/* vcvtneps2bf16 doesn't honor SNAN, turns sNAN into qNAN quietly, it
+   always rounds to nearest even and flushes input denormals to zero.
+   flag_unsafe_math_optimization is needed for vcvtneps2bf16 emulation
+   which doesn't support sNAN and qNAN.  */
+(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..1f763905082 100644
--- a/gcc/testsuite/gcc.target/i386/truncsfbf-1.c
+++ b/gcc/testsuite/gcc.target/i386/truncsfbf-1.c
@@ -1,6 +1,46 @@
 /* { dg-do compile } */
-/* { dg-options "-msse2 -O2 -ffast-math" } */
-/* { dg-final { scan-assembler-times "psrld" 1 } } */
+/* { dg-options "-msse2 -O2 -ffast-math -mtune=generic -mno-avxneconvert -mno-avx512bf16" } */
+/* 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.LFB0:
+ia32	.cfi_startproc
+ia32	movl	4\(%esp\), %edx
+ia32	movl	%edx, %eax
+ia32	shrl	\$16, %eax
+ia32	movl	%eax, %ecx
+ia32	andl	\$1, %eax
+ia32	leal	32767\(%edx,%eax\), %eax
+ia32	andl	\$32768, %ecx
+ia32	shrl	\$16, %eax
+ia32	andl	\$2139095040, %edx
+ia32	cmove	%ecx, %eax
+ia32	(v|)movd	%eax, %xmm0
+ia32	ret
+ia32...
+*ia32
+
+x64foo:
+x64.LFB0:
+x64	.cfi_startproc
+x64	(v|)movd	%xmm0, %edx
+x64	movl	%edx, %eax
+x64	shrl	\$16, %eax
+x64	movl	%eax, %ecx
+x64	andl	\$1, %eax
+x64	leal	32767\(%rdx,%rax\), %eax
+x64	andl	\$32768, %ecx
+x64	shrl	\$16, %eax
+x64	andl	\$2139095040, %edx
+x64	cmove	%ecx, %eax
+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..c33ad8b0df6 100644
--- a/gcc/testsuite/gcc.target/i386/truncsfbf-2.c
+++ b/gcc/testsuite/gcc.target/i386/truncsfbf-2.c
@@ -1,25 +1,39 @@
 /* { 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));
-  bits >>= 16;
-  uint16_t bfloat16_bits = (uint16_t) bits;
+  /* Flush input denormal to zero like vcvtneps2bf16.  */
+  if ((bits & 0x7f800000) == 0)
+    {
+      bits >>= 16;
+      bits &= 0x8000;
+    }
+  else
+    {
+      uint32_t rounding_bias = 0x7FFF + ((bits >> 16) & 1);
+      bits += rounding_bias;
+      bits >>= 16;
+    }
   __bf16 bf16;
+  uint16_t bfloat16_bits = (uint16_t) bits;
   memcpy (&bf16, &bfloat16_bits, sizeof (bf16));
   return bf16;
 }
@@ -27,10 +41,15 @@ CALC (float *a)
 int
 main (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 };
+  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, 1.170903e-38, -1.170903e-38, 1.175494e-38,
+      -1.175494e-38, 1.170902e-38, 4.999295e-39, 1.175494e-38
+    };
   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 +59,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 +81,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

Reply via email to