[v4,GCC13] RISC-V: Provide `fmin'/`fmax' RTL patterns

Message ID alpine.DEB.2.20.2202081225030.11348@tpp.orcam.me.uk
State Committed
Commit 9ddd44b58649d1d1e932c1e95dc00d654733c1fc
Headers
Series [v4,GCC13] RISC-V: Provide `fmin'/`fmax' RTL patterns |

Commit Message

Maciej W. Rozycki Feb. 8, 2022, 12:35 p.m. UTC
  As at r2.2 of the RISC-V ISA specification[1] (equivalent to version 2.0 
of the "F" and "D" standard architecture extensions for single-precision 
and double-precision floating-point respectively) the FMIN and FMAX 
machine instructions fully match our requirement for the `fminM3' and 
`fmaxM3' standard RTL patterns:

"For FMIN and FMAX, if at least one input is a signaling NaN, or if both 
inputs are quiet NaNs, the result is the canonical NaN.  If one operand 
is a quiet NaN and the other is not a NaN, the result is the non-NaN 
operand."

suitably for the IEEE 754-2008 `minNum' and `maxNum' operations.

However we only define `sminM3' and `smaxM3' standard RTL patterns to 
produce the FMIN and FMAX machine instructions, which in turn causes the 
`__builtin_fmin' and `__builtin_fmax' family of intrinsics to emit the 
corresponding libcalls rather than the relevant machine instructions.  
This is according to earlier revisions of the RISC-V ISA specification, 
which we however do not support anymore, as from commit 4b81528241ca 
("RISC-V: Support version controling for ISA standard extensions").

As from r20190608 of the RISC-V ISA specification (equivalent to version 
2.2 of the "F" and "D" standard ISA extensions for single-precision and 
double-precision floating-point respectively) the definition of the FMIN 
and FMAX machine instructions has been updated[2]:

"Defined the signed-zero behavior of FMIN.fmt and FMAX.fmt, and changed 
their behavior on signaling-NaN inputs to conform to the minimumNumber 
and maximumNumber operations in the proposed IEEE 754-201x 
specification."

and specifically[3]:

"Floating-point minimum-number and maximum-number instructions FMIN.S 
and FMAX.S write, respectively, the smaller or larger of rs1 and rs2 to 
rd.  For the purposes of these instructions only, the value -0.0 is 
considered to be less than the value +0.0.  If both inputs are NaNs, the 
result is the canonical NaN.  If only one operand is a NaN, the result 
is the non-NaN operand.  Signaling NaN inputs set the invalid operation 
exception flag, even when the result is not NaN."

Consequently for forwards compatibility with r20190608+ hardware we 
cannot use the FMIN and FMAX machine instructions unconditionally even 
where the ISA level of r2.2 has been specified with the `-misa-spec=2.2' 
option where operation would be different between ISA revisions, that 
is the handling of signaling NaN inputs.

Therefore provide new `fmin<mode>3' and `fmax<mode>3' patterns removing 
the need to emit libcalls with the `__builtin_fmin' and `__builtin_fmax' 
family of intrinsics, however limit them to where `-fno-signaling-nans' 
is in effect, deferring to other code generation strategies otherwise as 
applicable.  Use newly-defined UNSPECs as the operation codes so that 
the patterns are only ever used if referred to by their names, as there
is no RTL expression defined for the IEEE 754-2008 `minNum' and `maxNum' 
operations.

References:

[1] "The RISC-V Instruction Set Manual, Volume I: User-Level ISA",
    Document Version 2.2, May 7, 2017, Section 8.3 "NaN Generation and 
    Propagation", p. 48

[1] "The RISC-V Instruction Set Manual, Volume I: Unprivileged ISA",
    Document Version 20190608-Base-Ratified, June 8, 2019, "Preface",
    p. ii

[2] same, Section 11.6 "Single-Precision Floating-Point Computational
    Instructions", p. 66

	gcc/
	* config/riscv/riscv.md (UNSPEC_FMIN, UNSPEC_FMAX): New
	constants.
	(fmin<mode>3, fmax<mode>3): New insns.

	gcc/testsuite/
	* gcc.target/riscv/fmax-snan.c: New test.
	* gcc.target/riscv/fmax.c: New test.
	* gcc.target/riscv/fmaxf-snan.c: New test.
	* gcc.target/riscv/fmaxf.c: New test.
	* gcc.target/riscv/fmin-snan.c: New test.
	* gcc.target/riscv/fmin.c: New test.
	* gcc.target/riscv/fminf-snan.c: New test.
	* gcc.target/riscv/fminf.c: New test.
	* gcc.target/riscv/smax-ieee.c: New test.
	* gcc.target/riscv/smax.c: New test.
	* gcc.target/riscv/smaxf-ieee.c: New test.
	* gcc.target/riscv/smaxf.c: New test.
	* gcc.target/riscv/smin-ieee.c: New test.
	* gcc.target/riscv/smin.c: New test.
	* gcc.target/riscv/sminf-ieee.c: New test.
	* gcc.target/riscv/sminf.c: New test.
---
Hi,

 For the record here's v4 of this change as I observed an issue with the 
quotation of literal `.' in `scan-assembler' regexps.  As TCL strips one 
level of quotations before handing the string to the regexp interpreter 
any backslash characters need to be doubled to be received by the regexp 
parser.  The updated test cases have been verified to still pass.

  Maciej

Changes from v3:

- Quote `\' characters in `scan-assembler' for literal `.' in regexp.

- Mechanically update ChangeLog to have its entries separately for the 
  testsuite.

Changes from v2:

- Use UNSPECs for the `fmin'/`fmax' patterns, so that they cannot be 
  matched by the RTL expression.

- Revert the `HONOR_SNANS (<MODE>mode)' restriction for the `smin'/`smax'
  patterns; it was wrong, because HONOR_SNANS implies HONOR_NANS, and that 
  is not true for `-ffinite-math-only' where `smin'/`smax' still have to 
  work.

- Add test cases.

- Further clarify ISA/extension revisions/versions.

Changes from v1:

- Adjust heading from "RISC-V: Replace `smin'/`smax' RTL patterns with 
  `fmin'/`fmax'".

- Do not remove `smin'/`smax' patterns; instead make them available if
  `HONOR_SNANS (<MODE>mode)'.

- Make `fmin'/`fmax' patterns available if `!HONOR_SNANS (<MODE>mode)' 
  only.

- Update description accordingly; refer r2.2 and r20190608 ISA specs.
---
 gcc/config/riscv/riscv.md                   |   22 ++++++++++++++++++++++
 gcc/testsuite/gcc.target/riscv/fmax-snan.c  |   12 ++++++++++++
 gcc/testsuite/gcc.target/riscv/fmax.c       |   12 ++++++++++++
 gcc/testsuite/gcc.target/riscv/fmaxf-snan.c |   12 ++++++++++++
 gcc/testsuite/gcc.target/riscv/fmaxf.c      |   12 ++++++++++++
 gcc/testsuite/gcc.target/riscv/fmin-snan.c  |   12 ++++++++++++
 gcc/testsuite/gcc.target/riscv/fmin.c       |   12 ++++++++++++
 gcc/testsuite/gcc.target/riscv/fminf-snan.c |   12 ++++++++++++
 gcc/testsuite/gcc.target/riscv/fminf.c      |   12 ++++++++++++
 gcc/testsuite/gcc.target/riscv/smax-ieee.c  |   12 ++++++++++++
 gcc/testsuite/gcc.target/riscv/smax.c       |   12 ++++++++++++
 gcc/testsuite/gcc.target/riscv/smaxf-ieee.c |   12 ++++++++++++
 gcc/testsuite/gcc.target/riscv/smaxf.c      |   12 ++++++++++++
 gcc/testsuite/gcc.target/riscv/smin-ieee.c  |   12 ++++++++++++
 gcc/testsuite/gcc.target/riscv/smin.c       |   12 ++++++++++++
 gcc/testsuite/gcc.target/riscv/sminf-ieee.c |   12 ++++++++++++
 gcc/testsuite/gcc.target/riscv/sminf.c      |   12 ++++++++++++
 17 files changed, 214 insertions(+)

gcc-riscv-fmin-fmax.diff
  

Comments

Jim Wilson March 1, 2022, 3:43 a.m. UTC | #1
On Tue, Feb 8, 2022 at 4:35 AM Maciej W. Rozycki <macro@embecosm.com> wrote:

>         gcc/
>         * config/riscv/riscv.md (UNSPEC_FMIN, UNSPEC_FMAX): New
>         constants.
>         (fmin<mode>3, fmax<mode>3): New insns.
> ...


I tried testing on some of the hardware I have.  Both the HiFive Unleashed
(2018) and HiFive Unmatched (2021) implement the current definition of
fmin/fmax.  But the Allwinner Nezha (2021) implements the previous
definition of fmin/fmax.  SiFive was involved with the fmin/fmax change, so
it isn't surprising that they implemented the new semantics before other
companies.  The Nezha board with the T-Head C906 is a popular one, so we do
need to continue to support the 2017 spec, which your patch does with the
HONORS_SNAN checks.  I agree that we don't need to worry about spec
versions older than that.

This looks OK to me.

I've got builds running in parallel on the Unleashed and Unmatched to test
but that will take a couple of days and I don't expect any problems since
you already tested it.  I could do a build on the Nezha if I had to, but
that would take at least a week as it is a much slower board and I'd rather
not do that unless I have to.  This is hardware implementing the older spec
that you probably haven't tested though.

Jim
  

Patch

Index: gcc/gcc/config/riscv/riscv.md
===================================================================
--- gcc.orig/gcc/config/riscv/riscv.md
+++ gcc/gcc/config/riscv/riscv.md
@@ -42,6 +42,8 @@ 
   UNSPEC_COPYSIGN
   UNSPEC_LRINT
   UNSPEC_LROUND
+  UNSPEC_FMIN
+  UNSPEC_FMAX
 
   ;; Stack tie
   UNSPEC_TIE
@@ -1216,6 +1218,26 @@ 
 ;;
 ;;  ....................
 
+(define_insn "fmin<mode>3"
+  [(set (match_operand:ANYF                    0 "register_operand" "=f")
+	(unspec:ANYF [(use (match_operand:ANYF 1 "register_operand" " f"))
+		      (use (match_operand:ANYF 2 "register_operand" " f"))]
+		     UNSPEC_FMIN))]
+  "TARGET_HARD_FLOAT && !HONOR_SNANS (<MODE>mode)"
+  "fmin.<fmt>\t%0,%1,%2"
+  [(set_attr "type" "fmove")
+   (set_attr "mode" "<UNITMODE>")])
+
+(define_insn "fmax<mode>3"
+  [(set (match_operand:ANYF                    0 "register_operand" "=f")
+	(unspec:ANYF [(use (match_operand:ANYF 1 "register_operand" " f"))
+		      (use (match_operand:ANYF 2 "register_operand" " f"))]
+		     UNSPEC_FMAX))]
+  "TARGET_HARD_FLOAT && !HONOR_SNANS (<MODE>mode)"
+  "fmax.<fmt>\t%0,%1,%2"
+  [(set_attr "type" "fmove")
+   (set_attr "mode" "<UNITMODE>")])
+
 (define_insn "smin<mode>3"
   [(set (match_operand:ANYF            0 "register_operand" "=f")
 	(smin:ANYF (match_operand:ANYF 1 "register_operand" " f")
Index: gcc/gcc/testsuite/gcc.target/riscv/fmax-snan.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/fmax-snan.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fno-finite-math-only -fsigned-zeros -fsignaling-nans -dp" } */
+
+double
+fmax (double x, double y)
+{
+  return __builtin_fmax (x, y);
+}
+
+/* { dg-final { scan-assembler-not "\tfmax\\.d\t" } } */
+/* { dg-final { scan-assembler-not "\tfge\\.d\t" } } */
+/* { dg-final { scan-assembler "\t(call|tail)\tfmax\t" } } */
Index: gcc/gcc/testsuite/gcc.target/riscv/fmax.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/fmax.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fno-finite-math-only -fsigned-zeros -fno-signaling-nans -dp" } */
+
+double
+fmax (double x, double y)
+{
+  return __builtin_fmax (x, y);
+}
+
+/* { dg-final { scan-assembler-not "\ttail\tfmax\t" } } */
+/* { dg-final { scan-assembler-not "\tfge\\.d\t" } } */
+/* { dg-final { scan-assembler "\tfmax\\.d\t\[^\n\]* fmaxdf3\n" } } */
Index: gcc/gcc/testsuite/gcc.target/riscv/fmaxf-snan.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/fmaxf-snan.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fno-finite-math-only -fsigned-zeros -fsignaling-nans -dp" } */
+
+float
+fmaxf (float x, float y)
+{
+  return __builtin_fmaxf (x, y);
+}
+
+/* { dg-final { scan-assembler-not "\tfmax\\.s\t" } } */
+/* { dg-final { scan-assembler-not "\tfge\\.s\t" } } */
+/* { dg-final { scan-assembler "\t(call|tail)\tfmaxf\t" } } */
Index: gcc/gcc/testsuite/gcc.target/riscv/fmaxf.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/fmaxf.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fno-finite-math-only -fsigned-zeros -fno-signaling-nans -dp" } */
+
+float
+fmaxf (float x, float y)
+{
+  return __builtin_fmaxf (x, y);
+}
+
+/* { dg-final { scan-assembler-not "\ttail\tfmaxf\t" } } */
+/* { dg-final { scan-assembler-not "\tfge\\.s\t" } } */
+/* { dg-final { scan-assembler "\tfmax\\.s\t\[^\n\]* fmaxsf3\n" } } */
Index: gcc/gcc/testsuite/gcc.target/riscv/fmin-snan.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/fmin-snan.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fno-finite-math-only -fsigned-zeros -fsignaling-nans -dp" } */
+
+double
+fmin (double x, double y)
+{
+  return __builtin_fmin (x, y);
+}
+
+/* { dg-final { scan-assembler-not "\tfmin\\.d\t" } } */
+/* { dg-final { scan-assembler-not "\tfle\\.d\t" } } */
+/* { dg-final { scan-assembler "\t(call|tail)\tfmin\t" } } */
Index: gcc/gcc/testsuite/gcc.target/riscv/fmin.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/fmin.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fno-finite-math-only -fsigned-zeros -fno-signaling-nans -dp" } */
+
+double
+fmin (double x, double y)
+{
+  return __builtin_fmin (x, y);
+}
+
+/* { dg-final { scan-assembler-not "\ttail\tfmin\t" } } */
+/* { dg-final { scan-assembler-not "\tfle\\.d\t" } } */
+/* { dg-final { scan-assembler "\tfmin\\.d\t\[^\n\]* fmindf3\n" } } */
Index: gcc/gcc/testsuite/gcc.target/riscv/fminf-snan.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/fminf-snan.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fno-finite-math-only -fsigned-zeros -fsignaling-nans -dp" } */
+
+float
+fminf (float x, float y)
+{
+  return __builtin_fminf (x, y);
+}
+
+/* { dg-final { scan-assembler-not "\tfmin\\.s\t" } } */
+/* { dg-final { scan-assembler-not "\tfle\\.s\t" } } */
+/* { dg-final { scan-assembler "\t(call|tail)\tfminf\t" } } */
Index: gcc/gcc/testsuite/gcc.target/riscv/fminf.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/fminf.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fno-finite-math-only -fsigned-zeros -fno-signaling-nans -dp" } */
+
+float
+fminf (float x, float y)
+{
+  return __builtin_fminf (x, y);
+}
+
+/* { dg-final { scan-assembler-not "\ttail\tfminf\t" } } */
+/* { dg-final { scan-assembler-not "\tfle\\.s\t" } } */
+/* { dg-final { scan-assembler "\tfmin\\.s\t\[^\n\]* fminsf3\n" } } */
Index: gcc/gcc/testsuite/gcc.target/riscv/smax-ieee.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/smax-ieee.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-ffinite-math-only -fsigned-zeros -dp" } */
+
+double
+smax (double x, double y)
+{
+  return x >= y ? x : y;
+}
+
+/* { dg-final { scan-assembler-not "\t(call|tail)\tfmax\t" } } */
+/* { dg-final { scan-assembler-not "\tfmax\\.d\t" } } */
+/* { dg-final { scan-assembler "\tfge\\.d\t" } } */
Index: gcc/gcc/testsuite/gcc.target/riscv/smax.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/smax.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-ffinite-math-only -fno-signed-zeros -dp" } */
+
+double
+smax (double x, double y)
+{
+  return x >= y ? x : y;
+}
+
+/* { dg-final { scan-assembler-not "\ttail\tfmax\t" } } */
+/* { dg-final { scan-assembler-not "\tfge\\.d\t" } } */
+/* { dg-final { scan-assembler "\tfmax\\.d\t\[^\n\]* smaxdf3\n" } } */
Index: gcc/gcc/testsuite/gcc.target/riscv/smaxf-ieee.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/smaxf-ieee.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-ffinite-math-only -fsigned-zeros -dp" } */
+
+float
+smaxf (float x, float y)
+{
+  return x >= y ? x : y;
+}
+
+/* { dg-final { scan-assembler-not "\t(call|tail)\tfmaxf\t" } } */
+/* { dg-final { scan-assembler-not "\tfmax\\.s\t" } } */
+/* { dg-final { scan-assembler "\tfge\\.s\t" } } */
Index: gcc/gcc/testsuite/gcc.target/riscv/smaxf.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/smaxf.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-ffinite-math-only -fno-signed-zeros -dp" } */
+
+float
+smaxf (float x, float y)
+{
+  return x >= y ? x : y;
+}
+
+/* { dg-final { scan-assembler-not "\ttail\tfmaxf\t" } } */
+/* { dg-final { scan-assembler-not "\tfge\\.s\t" } } */
+/* { dg-final { scan-assembler "\tfmax\\.s\t\[^\n\]* smaxsf3\n" } } */
Index: gcc/gcc/testsuite/gcc.target/riscv/smin-ieee.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/smin-ieee.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-ffinite-math-only -fsigned-zeros -dp" } */
+
+double
+smin (double x, double y)
+{
+  return x <= y ? x : y;
+}
+
+/* { dg-final { scan-assembler-not "\t(call|tail)\tfmin\t" } } */
+/* { dg-final { scan-assembler-not "\tfmin\\.d\t" } } */
+/* { dg-final { scan-assembler "\tfle\\.d\t" } } */
Index: gcc/gcc/testsuite/gcc.target/riscv/smin.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/smin.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-ffinite-math-only -fno-signed-zeros -dp" } */
+
+double
+smin (double x, double y)
+{
+  return x <= y ? x : y;
+}
+
+/* { dg-final { scan-assembler-not "\ttail\tfmin\t" } } */
+/* { dg-final { scan-assembler-not "\tfle\\.d\t" } } */
+/* { dg-final { scan-assembler "\tfmin\\.d\t\[^\n\]* smindf3\n" } } */
Index: gcc/gcc/testsuite/gcc.target/riscv/sminf-ieee.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/sminf-ieee.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-ffinite-math-only -fsigned-zeros -dp" } */
+
+float
+sminf (float x, float y)
+{
+  return x <= y ? x : y;
+}
+
+/* { dg-final { scan-assembler-not "\t(call|tail)\tfminf\t" } } */
+/* { dg-final { scan-assembler-not "\tfmin\\.s\t" } } */
+/* { dg-final { scan-assembler "\tfle\\.s\t" } } */
Index: gcc/gcc/testsuite/gcc.target/riscv/sminf.c
===================================================================
--- /dev/null
+++ gcc/gcc/testsuite/gcc.target/riscv/sminf.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-ffinite-math-only -fno-signed-zeros -dp" } */
+
+float
+sminf (float x, float y)
+{
+  return x <= y ? x : y;
+}
+
+/* { dg-final { scan-assembler-not "\ttail\tfminf\t" } } */
+/* { dg-final { scan-assembler-not "\tfle\\.s\t" } } */
+/* { dg-final { scan-assembler "\tfmin\\.s\t\[^\n\]* sminsf3\n" } } */