On Mon, Aug 24, 2026 at 4:31 PM H.J. Lu <[email protected]> wrote: > > 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. > >
Here is the v4 patch: 1. Mark input denormal branch as unlikely. 2. Remove -mtune=generic from truncsfbf-1.c. > 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. -- H.J.
From f9ba0274c12920ab04c35cdf42c3aa1df3246e02 Mon Sep 17 00:00:00 2001 From: "H.J. Lu" <[email protected]> Date: Wed, 19 Aug 2026 11:42:38 +0800 Subject: [PATCH v4] 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 | 90 ++++++++++++++++++++ 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, 431 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..6360e4842cf 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); + + 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, + profile_probability::unlikely ()); + + /* 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..0348629bdb4 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 -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
