diff mbox series

tests/tcg: add float_madds test to multiarch

Message ID 20190913134935.29696-1-alex.bennee@linaro.org
State New
Headers show
Series tests/tcg: add float_madds test to multiarch | expand

Commit Message

Alex Bennée Sept. 13, 2019, 1:49 p.m. UTC
This is a generic floating point multiply and accumulate test for
single precision floating point values. I've split of the common float
functions into a helper library so additional tests can use the same
common code.

Signed-off-by: Alex Bennée <alex.bennee@linaro.org>

---
 tests/tcg/multiarch/Makefile.target |   7 +-
 tests/tcg/multiarch/float_helpers.c | 208 ++++++++++++++++++++++++++++
 tests/tcg/multiarch/float_helpers.h |  26 ++++
 tests/tcg/multiarch/float_madds.c   |  78 +++++++++++
 4 files changed, 318 insertions(+), 1 deletion(-)
 create mode 100644 tests/tcg/multiarch/float_helpers.c
 create mode 100644 tests/tcg/multiarch/float_helpers.h
 create mode 100644 tests/tcg/multiarch/float_madds.c

-- 
2.20.1

Comments

Paul Clarke Sept. 13, 2019, 2:50 p.m. UTC | #1
On 9/13/19 8:49 AM, Alex Bennée wrote:
> +static float f32_numbers[] = {

> +    -SNANF,

> +    -NAN,

> +    -INFINITY,

> +    -FLT_MAX,

> +    -1.111E+31,

> +    -1.111E+30,

> +    -1.08700982e-12,

> +    -1.78051176e-20,

> +    -FLT_MIN,

> +    0.0,

> +    FLT_MIN,

> +    2.98023224e-08,

> +    5.96046E-8, /* min positive FP16 subnormal */

> +    6.09756E-5, /* max subnormal FP16 */

> +    6.10352E-5, /* min positive normal FP16 */

> +    1.0,

> +    1.0009765625, /* smallest float after 1.0 FP16 */

> +    2.0,

> +    M_E, M_PI,

> +    65503.0,

> +    65504.0, /* max FP16 */

> +    65505.0,

> +    131007.0,

> +    131008.0, /* max AFP */

> +    131009.0,

> +    1.111E+30,

> +    FLT_MAX,

> +    INFINITY,

> +    NAN,

> +    SNANF

> +};


I've noticed that Glibc prefers to use hex representation for float values, to ensure an accurate representation.  If you care to do so, here they are:
static float f32_numbers[] = {
    -SNANF, 
    -NAN,   
    -INFINITY,
    -FLT_MAX,
    -0x1.1874b2p+103,
    -0x1.c0bab6p+99,
    -0x1.31f75p-40,
    -0x1.505444p-66,
    -FLT_MIN,
    0.0,    
    FLT_MIN,
    0x1p-25,
    0x1.ffffe6p-25, /* min positive FP16 subnormal */
    0x1.ff801ap-15, /* max subnormal FP16 */
    0x1.00000cp-14, /* min positive normal FP16 */
    1.0,    
    0x1.004p+0, /* smallest float after 1.0 FP16 */
    2.0,    
    M_E, M_PI,
    0x1.ffbep+15,
    0x1.ffcp+15, /* max FP16 */
    0x1.ffc2p+15,
    0x1.ffbfp+16,
    0x1.ffcp+16, /* max AFP */
    0x1.ffc1p+16,
    0x1.c0bab6p+99,
    FLT_MAX,
    INFINITY,
    NAN,    
    SNANF   
};

PC
Richard Henderson Sept. 14, 2019, 3:12 p.m. UTC | #2
On 9/13/19 9:49 AM, Alex Bennée wrote:
> +                    /* must be built with -O2 to generate fused op */

> +                    r = a * b + c;


I would prefer to use fmaf() or __builtin_fmaf() here.

Although you'll need to link with -lm just in case the
target doesn't support an instruction for fmaf and so
the builtin expands to the standard library call.

I also like Paul's suggestion to use hex float constants.


r~
Alex Bennée Sept. 14, 2019, 5:59 p.m. UTC | #3
Richard Henderson <richard.henderson@linaro.org> writes:

> On 9/13/19 9:49 AM, Alex Bennée wrote:

>> +                    /* must be built with -O2 to generate fused op */

>> +                    r = a * b + c;

>

> I would prefer to use fmaf() or __builtin_fmaf() here.

>

> Although you'll need to link with -lm just in case the

> target doesn't support an instruction for fmaf and so

> the builtin expands to the standard library call.


I can do that - we have other tests that link to libm.

I was expecting to see more breakage but the ppc64 tests all passed (or
at least against the power8 David ran it on). What am I missing to hit
the cases you know are broken?

I've also experimented with reducing the number of iterations because if
we want to have golden references we probably don't want to dump several
hundred kilobytes of "golden" references into the source tree.

> I also like Paul's suggestion to use hex float constants.


Hmm I guess - look a bit weird but I guess that's lack of familiarity.
Is is still normalised? I guess the frac shows up (without the implicit
bit).

>

>

> r~



--
Alex Bennée
Richard Henderson Sept. 14, 2019, 6:11 p.m. UTC | #4
On 9/14/19 1:59 PM, Alex Bennée wrote:
> 

> Richard Henderson <richard.henderson@linaro.org> writes:

> 

>> On 9/13/19 9:49 AM, Alex Bennée wrote:

>>> +                    /* must be built with -O2 to generate fused op */

>>> +                    r = a * b + c;

>>

>> I would prefer to use fmaf() or __builtin_fmaf() here.

>>

>> Although you'll need to link with -lm just in case the

>> target doesn't support an instruction for fmaf and so

>> the builtin expands to the standard library call.

> 

> I can do that - we have other tests that link to libm.

> 

> I was expecting to see more breakage but the ppc64 tests all passed (or

> at least against the power8 David ran it on). What am I missing to hit

> the cases you know are broken?


I would *expect* the test to pass when run natively on power8 hardware.  Did it
not fail when run via qemu?  If not, then we didn't really choose the argument
sets that are affected by double rounding.

I would expect the inputs that Paul used in the original report to be
candidates.  Otherwise, we should grab some from the glibc fma test case(s).

> I've also experimented with reducing the number of iterations because if

> we want to have golden references we probably don't want to dump several

> hundred kilobytes of "golden" references into the source tree.

> 

>> I also like Paul's suggestion to use hex float constants.

> 

> Hmm I guess - look a bit weird but I guess that's lack of familiarity.

> Is is still normalised? I guess the frac shows up (without the implicit

> bit).


The implicit bit is there: 0x1.xxx.  The representation is always normalized;
you write denormal numbers by using an exponent that would require denormalization.


r~
diff mbox series

Patch

diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target
index 657a04f802d..0446b75c456 100644
--- a/tests/tcg/multiarch/Makefile.target
+++ b/tests/tcg/multiarch/Makefile.target
@@ -10,12 +10,17 @@  MULTIARCH_SRC=$(SRC_PATH)/tests/tcg/multiarch
 # Set search path for all sources
 VPATH 		+= $(MULTIARCH_SRC)
 MULTIARCH_SRCS   =$(notdir $(wildcard $(MULTIARCH_SRC)/*.c))
-MULTIARCH_TESTS  =$(MULTIARCH_SRCS:.c=)
+MULTIARCH_TESTS  =$(filter-out float_helpers, $(MULTIARCH_SRCS:.c=))
 
 #
 # The following are any additional rules needed to build things
 #
 
+
+float_madds: LDFLAGS+=-lm
+float_madds: float_madds.c float_helpers.c
+	$(CC) $(CFLAGS) $(EXTRA_CFLAGS) -O2 $< $(MULTIARCH_SRC)/float_helpers.c -o $@ $(LDFLAGS)
+
 testthread: LDFLAGS+=-lpthread
 
 # We define the runner for test-mmap after the individual
diff --git a/tests/tcg/multiarch/float_helpers.c b/tests/tcg/multiarch/float_helpers.c
new file mode 100644
index 00000000000..481d8d33317
--- /dev/null
+++ b/tests/tcg/multiarch/float_helpers.c
@@ -0,0 +1,208 @@ 
+/*
+ * Common Float Helpers
+ *
+ * This contains a series of useful utility routines and a set of
+ * floating point constants useful for exercising the edge cases in
+ * floating point tests.
+ *
+ * Copyright (c) 2019 Linaro
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/* we want additional float type definitions */
+#define __STDC_WANT_IEC_60559_BFP_EXT__
+#define __STDC_WANT_IEC_60559_TYPES_EXT__
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <inttypes.h>
+#include <math.h>
+#include <float.h>
+#include <fenv.h>
+
+#include "float_helpers.h"
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+/*
+ * Half Precision Numbers
+ *
+ * Not yet well standardised so we return a plain uint16_t for now.
+ */
+
+/* no handy defines for these numbers */
+static uint16_t f16_numbers[] = {
+    0xffff, /* -NaN / AHP -Max */
+    0xfcff, /* -NaN / AHP */
+    0xfc01, /* -NaN / AHP */
+    0xfc00, /* -Inf */
+    0xfbff, /* -Max */
+    0xc000, /* -2 */
+    0xbc00, /* -1 */
+    0x8001, /* -MIN subnormal */
+    0x8000, /* -0 */
+    0x0000, /* +0 */
+    0x0001, /* MIN subnormal */
+    0x3c00, /* 1 */
+    0x7bff, /* Max */
+    0x7c00, /* Inf */
+    0x7c01, /* NaN / AHP */
+    0x7cff, /* NaN / AHP */
+    0x7fff, /* NaN / AHP +Max*/
+};
+
+const int num_f16 = ARRAY_SIZE(f16_numbers);
+
+uint16_t get_f16(int i) {
+    return f16_numbers[i % num_f16];
+}
+
+/* only display as hex */
+char *fmt_16(uint16_t num) {
+    char *fmt;
+    asprintf(&fmt, "f16 %#04x", num);
+    return fmt;
+}
+
+/*
+ * Single Precision Numbers
+ */
+
+#ifndef SNANF
+/* Signaling NaN macros, if supported.  */
+# if __GNUC_PREREQ(3, 3)
+#  define SNANF (__builtin_nansf (""))
+#  define SNAN (__builtin_nans (""))
+#  define SNANL (__builtin_nansl (""))
+# endif
+#endif
+
+static float f32_numbers[] = {
+    -SNANF,
+    -NAN,
+    -INFINITY,
+    -FLT_MAX,
+    -1.111E+31,
+    -1.111E+30,
+    -1.08700982e-12,
+    -1.78051176e-20,
+    -FLT_MIN,
+    0.0,
+    FLT_MIN,
+    2.98023224e-08,
+    5.96046E-8, /* min positive FP16 subnormal */
+    6.09756E-5, /* max subnormal FP16 */
+    6.10352E-5, /* min positive normal FP16 */
+    1.0,
+    1.0009765625, /* smallest float after 1.0 FP16 */
+    2.0,
+    M_E, M_PI,
+    65503.0,
+    65504.0, /* max FP16 */
+    65505.0,
+    131007.0,
+    131008.0, /* max AFP */
+    131009.0,
+    1.111E+30,
+    FLT_MAX,
+    INFINITY,
+    NAN,
+    SNANF
+};
+
+const int num_f32 = ARRAY_SIZE(f32_numbers);
+
+float get_f32(int i) {
+    return f32_numbers[i % num_f32];
+}
+
+char *fmt_f32(float num) {
+    uint32_t single_as_hex = *(uint32_t *) &num;
+    char *fmt;
+    asprintf(&fmt, "f32 %02.20e / %#010x", num, single_as_hex);
+    return fmt;
+}
+
+
+/* This allows us to initialise some doubles as pure hex */
+typedef union {
+    double d;
+    uint64_t h;
+} test_doubles;
+
+static test_doubles f64_numbers[] = {
+    {SNAN},
+    {-NAN},
+    {-INFINITY},
+    {-DBL_MAX},
+    {-FLT_MAX-1.0},
+    {-FLT_MAX},
+    {-1.111E+31},
+    {-1.111E+30}, /* half prec */
+    {-2.0}, {-1.0},
+    {-DBL_MIN},
+    {-FLT_MIN},
+    {0.0},
+    {FLT_MIN},
+    {2.98023224e-08},
+    {5.96046E-8}, /* min positive FP16 subnormal */
+    {6.09756E-5}, /* max subnormal FP16 */
+    {6.10352E-5}, /* min positive normal FP16 */
+    {1.0},
+    {1.0009765625}, /* smallest float after 1.0 FP16 */
+    {DBL_MIN},
+    {1.3789972848607228e-308},
+    {1.4914738736681624e-308},
+    {1.0}, {2.0},
+    {M_E}, {M_PI},
+    {65503.0},
+    {65504.0}, /* max FP16 */
+    {65505.0},
+    {131007.0},
+    {131008.0}, /* max AFP */
+    {131009.0},
+    {.h = 0x41dfffffffc00000 }, /* to int = 0x7fffffff */
+    {FLT_MAX},
+    {FLT_MAX + 1.0},
+    {DBL_MAX},
+    {INFINITY},
+    {NAN},
+    {.h = 0x7ff0000000000001}, /* SNAN */
+    {SNAN},
+};
+
+const int num_f64 = ARRAY_SIZE(f64_numbers);
+
+double get_f64(int i) {
+    return f64_numbers[i % num_f64].d;
+}
+
+char *fmt_f64(double num) {
+    uint64_t double_as_hex = *(uint64_t *) &num;
+    char *fmt;
+    asprintf(&fmt, "f64 %02.20e / %#020" PRIx64, num, double_as_hex);
+    return fmt;
+}
+
+/*
+ * Float flags
+ */
+char *fmt_flags(void)
+{
+    int flags = fetestexcept(FE_ALL_EXCEPT);
+    char *fmt;
+
+    if (flags) {
+        asprintf(&fmt, "%s%s%s%s%s",
+                 flags & FE_OVERFLOW ? "OVERFLOW " : "",
+                 flags & FE_UNDERFLOW ? "UNDERFLOW " : "",
+                 flags & FE_DIVBYZERO ? "DIV0 " : "",
+                 flags & FE_INEXACT ? "INEXACT " : "",
+                 flags & FE_INVALID ? "INVALID" : "");
+    } else {
+        asprintf(&fmt, "OK");
+    }
+
+    return fmt;
+}
diff --git a/tests/tcg/multiarch/float_helpers.h b/tests/tcg/multiarch/float_helpers.h
new file mode 100644
index 00000000000..4a1e2f3853a
--- /dev/null
+++ b/tests/tcg/multiarch/float_helpers.h
@@ -0,0 +1,26 @@ 
+/*
+ * Common Float Helpers
+ *
+ * Copyright (c) 2019 Linaro
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <inttypes.h>
+
+/* Number of constants in each table */
+extern const int num_f16;
+extern const int num_f32;
+extern const int num_f64;
+
+/* Accessor helpers */
+uint16_t get_f16(int i); /* use _Float16 when we can */
+float    get_f32(int i);
+double   get_f64(int i);
+
+/* Return format strings, free after use */
+char * fmt_f16(uint16_t);
+char * fmt_f32(float);
+char * fmt_f64(double);
+/* exception flags */
+char * fmt_flags(void);
diff --git a/tests/tcg/multiarch/float_madds.c b/tests/tcg/multiarch/float_madds.c
new file mode 100644
index 00000000000..bc11eea9084
--- /dev/null
+++ b/tests/tcg/multiarch/float_madds.c
@@ -0,0 +1,78 @@ 
+/*
+ * Fused Multiply Add (Single)
+ *
+ * Copyright (c) 2019 Linaro
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <float.h>
+#include <fenv.h>
+
+#include "float_helpers.h"
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+typedef struct {
+    int flag;
+    char *desc;
+} float_mapping;
+
+float_mapping round_flags[] = {
+    { FE_TONEAREST, "to nearest" },
+    { FE_UPWARD, "upwards" },
+    { FE_DOWNWARD, "downwards" },
+    { FE_TOWARDZERO, "to zero" }
+};
+
+void print_result(float a, float b, float c, float r)
+{
+    char *a_fmt, *b_fmt, *c_fmt, *r_fmt, *flag_fmt;
+
+    a_fmt = fmt_f32(a);
+    b_fmt = fmt_f32(b);
+    c_fmt = fmt_f32(c);
+    r_fmt = fmt_f32(r);
+    flag_fmt = fmt_flags();
+
+    printf("%s * %s + %s = %s  (%s)\n",
+           a_fmt, b_fmt, c_fmt, r_fmt, flag_fmt);
+
+    free(a_fmt);
+    free(b_fmt);
+    free(c_fmt);
+    free(r_fmt);
+    free(flag_fmt);
+}
+
+
+int main(int argc, char *argv[argc])
+{
+    int i, j, k, l;
+    float a, b, c, r;
+
+    for (i = 0; i < ARRAY_SIZE(round_flags); ++i) {
+        fesetround(round_flags[i].flag);
+        printf("### Rounding %s\n", round_flags[i].desc);
+        for (j = 0; j < num_f32; j += 3) {
+            for (k = 1; k < num_f32; k += 3 ) {
+                for (l = 2; l < num_f32; l += 3) {
+                    a = get_f32(j);
+                    b = get_f32(k);
+                    c = get_f32(l);
+                    feclearexcept(FE_ALL_EXCEPT);
+
+                    /* must be built with -O2 to generate fused op */
+                    r = a * b + c;
+
+                    print_result(a, b, c, r);
+                }
+            }
+        }
+    }
+
+    return 0;
+}