On Tue, Sep 29, 2026 at 3:40 AM <[email protected]> wrote:
>
> From: Kyrylo Tkachov <[email protected]>
>
> If-conversion can expose separate comparisons for a predicate and its
> inverse. Value number selects using an available inverse comparison
> with exchanged arms. This also exposes equivalent scalar selects to
> CSE and lets their users simplify in the same VN pass.
>
> For example, with finite-math assumptions:
>
>   p = x < y;
>   a = p ? v1 : v2;
>   q = x >= y;
>   b = q ? v2 : v1;
>   r = a ^ b;
>
> becomes:
>
>   r = 0;
>
> Look up the exchanged COND_EXPR in visit_nary_op. If no equivalent
> select exists, use an available same-block inverse to build its value
> number. Prefer the lower comparison code for this new expression,
> including after VN operand canonicalization, to keep the choice stable.
> Extend the existing insertion code to materialize COND_EXPR after
> checking availability of all three operands.
>
> Use invert_tree_comparison for NaN semantics. Keep a nontrapping guard
> because EQ/NE inversion can be valid even when signaling NaNs can trap.
> Reusing a later comparison must not move an earlier trap past a side effect.
>
> This removes one comparison and one mask copy from each hot vector loop
> in miniBUDE 20210901 on Neoverse V2 and Olympus.
>
> Bootstrapped and tested on aarch64-none-linux-gnu.
> Ok for trunk?
> Thanks,
> Kyrill
>
> gcc/ChangeLog:
>
>         * tree-ssa-sccvn.cc (visit_nary_op): Value number selects using
>         inverse comparisons and exchanged arms.
>         (eliminate_dom_walker::eliminate_insert): Handle COND_EXPR.
>
> gcc/testsuite/ChangeLog:
>
>         * gcc.dg/tree-ssa/fre-inverse-compare-1.c: New test.
>         * gcc.dg/tree-ssa/fre-inverse-compare-2.c: Likewise.
>         * gcc.dg/tree-ssa/fre-inverse-compare-3.c: Likewise.
>         * gcc.dg/tree-ssa/ifc-cmp-inverse-1.c: Likewise.
>         * gcc.dg/tree-ssa/ifc-cmp-inverse-2.c: Likewise.
>         * gcc.dg/tree-ssa/ifc-cmp-inverse-3.c: Likewise.
>         * gcc.dg/tree-ssa/ifc-cmp-inverse-4.c: Likewise.
>         * gcc.dg/tree-ssa/ifc-cmp-inverse-5.c: Likewise.
>         * gcc.dg/tree-ssa/ifc-cmp-inverse-6.c: Likewise.
>         * gcc.dg/tree-ssa/ifc-cmp-inverse-7.c: Likewise.
>         * gcc.dg/tree-ssa/ifc-cmp-inverse-8.c: Likewise.
>
> Signed-off-by: Kyrylo Tkachov <[email protected]>
> ---
>  .../gcc.dg/tree-ssa/fre-inverse-compare-1.c   | 46 ++++++++++
>  .../gcc.dg/tree-ssa/fre-inverse-compare-2.c   | 26 ++++++
>  .../gcc.dg/tree-ssa/fre-inverse-compare-3.c   | 65 ++++++++++++++
>  .../gcc.dg/tree-ssa/ifc-cmp-inverse-1.c       | 35 ++++++++
>  .../gcc.dg/tree-ssa/ifc-cmp-inverse-2.c       | 33 +++++++
>  .../gcc.dg/tree-ssa/ifc-cmp-inverse-3.c       | 34 ++++++++
>  .../gcc.dg/tree-ssa/ifc-cmp-inverse-4.c       | 83 ++++++++++++++++++
>  .../gcc.dg/tree-ssa/ifc-cmp-inverse-5.c       | 60 +++++++++++++
>  .../gcc.dg/tree-ssa/ifc-cmp-inverse-6.c       | 69 +++++++++++++++
>  .../gcc.dg/tree-ssa/ifc-cmp-inverse-7.c       | 57 ++++++++++++
>  .../gcc.dg/tree-ssa/ifc-cmp-inverse-8.c       | 62 +++++++++++++
>  gcc/tree-ssa-sccvn.cc                         | 87 +++++++++++++++++++
>  12 files changed, 657 insertions(+)
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/fre-inverse-compare-1.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/fre-inverse-compare-2.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/fre-inverse-compare-3.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-1.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-2.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-3.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-4.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-5.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-6.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-7.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-8.c
>
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/fre-inverse-compare-1.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/fre-inverse-compare-1.c
> new file mode 100644
> index 00000000000..e986bc350f7
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/fre-inverse-compare-1.c
> @@ -0,0 +1,46 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fgimple -O2 -ffinite-math-only -fno-trapping-math 
> -fdump-tree-fre1 -fdump-tree-optimized" } */
> +
> +/* Reuse the available inverse comparison and swap the select arms.  */
> +
> +int __GIMPLE (ssa,startwith("fre1"))
> +scalar_select (float x, float y, int a, int b, int c, int d)
> +{
> +  _Bool p;
> +  _Bool q;
> +  int t;
> +  int u;
> +  int r;
> +
> +  __BB(2):
> +  p_7 = x_1(D) < y_2(D);
> +  t_8 = p_7 ? a_3(D) : b_4(D);
> +  q_9 = x_1(D) >= y_2(D);
> +  u_10 = q_9 ? c_5(D) : d_6(D);
> +  r_11 = t_8 + u_10;
> +  return r_11;
> +}
> +
> +/* Operand canonicalization must not make each select prefer the other's
> +   comparison.  Keep the available comparison with reversed operands.  */
> +
> +int __GIMPLE (ssa,startwith("fre1"))
> +reversed_select (float x, float y, int a, int b, int c, int d)
> +{
> +  _Bool p;
> +  _Bool q;
> +  int t;
> +  int u;
> +  int r;
> +
> +  __BB(2):
> +  q_7 = y_2(D) > x_1(D);
> +  t_8 = q_7 ? a_3(D) : b_4(D);
> +  p_9 = x_1(D) >= y_2(D);
> +  u_10 = p_9 ? c_5(D) : d_6(D);
> +  r_11 = t_8 + u_10;
> +  return r_11;
> +}
> +
> +/* { dg-final { scan-tree-dump-times {= [pq]_7 \? d_6\(D\) : c_5\(D\);} 2 
> "fre1" } } */
> +/* { dg-final { scan-tree-dump-not { >= } "optimized" } } */
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/fre-inverse-compare-2.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/fre-inverse-compare-2.c
> new file mode 100644
> index 00000000000..7b72aa3b126
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/fre-inverse-compare-2.c
> @@ -0,0 +1,26 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fgimple -O2 -ftrapping-math -fsignaling-nans 
> -fdump-tree-fre1" } */
> +
> +volatile int marker;
> +
> +int __GIMPLE (ssa,startwith("fre1"))
> +compare (float x, float y, int a, int b, int c, int d)
> +{
> +  _Bool p;
> +  _Bool q;
> +  int t;
> +  int u;
> +  int r;
> +
> +  __BB(2):
> +  p_7 = x_1(D) != y_2(D);
> +  marker = 1;
> +  q_8 = x_1(D) == y_2(D);
> +  t_9 = q_8 ? a_3(D) : b_4(D);
> +  u_10 = p_7 ? c_5(D) : d_6(D);
> +  r_11 = t_9 + u_10;
> +  return r_11;
> +}
> +
> +/* The earlier comparison must remain a condition of its select.  */
> +/* { dg-final { scan-tree-dump {u_10 = p_7 \? c_5\(D\) : d_6\(D\);} "fre1" } 
> } */
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/fre-inverse-compare-3.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/fre-inverse-compare-3.c
> new file mode 100644
> index 00000000000..0c6a1a068d8
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/fre-inverse-compare-3.c
> @@ -0,0 +1,65 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fgimple -O2 -ffinite-math-only -fno-trapping-math 
> -fdump-tree-fre1" } */
> +
> +/* Equivalent selects must have the same value number, so their users
> +   can also be simplified during the same FRE pass.  */
> +
> +int __GIMPLE (ssa,startwith("fre1"))
> +equivalent_selects (float x, float y, int a, int b)
> +{
> +  _Bool p;
> +  _Bool q;
> +  int t;
> +  int u;
> +  int r;
> +
> +  __BB(2):
> +  p_5 = x_1(D) < y_2(D);
> +  t_6 = p_5 ? a_3(D) : b_4(D);
> +  q_7 = x_1(D) >= y_2(D);
> +  u_8 = q_7 ? b_4(D) : a_3(D);
> +  r_9 = t_6 ^ u_8;
> +  return r_9;
> +}
> +
> +/* The inverse lookup must also accept reversed comparison operands.  */
> +
> +int __GIMPLE (ssa,startwith("fre1"))
> +reversed_operands (float x, float y, int a, int b)
> +{
> +  _Bool p;
> +  _Bool q;
> +  int t;
> +  int u;
> +  int r;
> +
> +  __BB(2):
> +  p_5 = y_2(D) > x_1(D);
> +  t_6 = p_5 ? a_3(D) : b_4(D);
> +  q_7 = x_1(D) >= y_2(D);
> +  u_8 = q_7 ? b_4(D) : a_3(D);
> +  r_9 = t_6 ^ u_8;
> +  return r_9;
> +}
> +
> +/* Find the same redundancy when the opposite select occurs first.  */
> +
> +int __GIMPLE (ssa,startwith("fre1"))
> +reversed_order (float x, float y, int a, int b)
> +{
> +  _Bool p;
> +  _Bool q;
> +  int t;
> +  int u;
> +  int r;
> +
> +  __BB(2):
> +  q_5 = x_1(D) >= y_2(D);
> +  t_6 = q_5 ? b_4(D) : a_3(D);
> +  p_7 = y_2(D) > x_1(D);
> +  u_8 = p_7 ? a_3(D) : b_4(D);
> +  r_9 = t_6 ^ u_8;
> +  return r_9;
> +}
> +
> +/* { dg-final { scan-tree-dump-times {return 0;} 3 "fre1" } } */
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-1.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-1.c
> new file mode 100644
> index 00000000000..8d291d0b789
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-1.c
> @@ -0,0 +1,35 @@
> +/* { dg-do compile } */
> +/* { dg-options "-Ofast -fdump-tree-ifcvt" } */
> +
> +/* The two merge points are predicated on DISTBB < 0 and on DISTBB >= 0.
> +   With NaNs out of the way the second predicate is the inverse of the
> +   first, so if-conversion has to reuse it with the arms of the select
> +   swapped instead of emitting a second comparison.  */
> +
> +void
> +f (float *restrict etot, const float *restrict lx, float radij, float 
> r_radij,
> +   float elcdst, float elcdst1, float distdslv, float r_distdslv,
> +   float chrg_init, float dslv_init, int n)
> +{
> +  for (int l = 0; l < n; l++)
> +    {
> +      float distij = lx[l];
> +      float distbb = distij - radij;
> +      int zone1 = distbb < 0.f;
> +
> +      etot[l] += (1.f - distij * r_radij) * (zone1 ? 76.f : 0.f);
> +
> +      float chrg_e = chrg_init
> +       * ((zone1 ? 1.f : 1.f - distbb * elcdst1)
> +          * (distbb < elcdst ? 1.f : 0.f));
> +      etot[l] += chrg_e * 45.f;
> +
> +      float coeff = 1.f - distbb * r_distdslv;
> +      float dslv_e = dslv_init * (distbb < distdslv ? 1.f : 0.f);
> +      dslv_e *= distbb >= 0.f ? coeff : 1.f;
> +      etot[l] += dslv_e;
> +    }
> +}
> +
> +/* { dg-final { scan-tree-dump-times {= \S+ < 0\.0;} 1 "ifcvt" } } */
> +/* { dg-final { scan-tree-dump-not {= \S+ >= 0\.0;} "ifcvt" } } */
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-2.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-2.c
> new file mode 100644
> index 00000000000..76eef5bf586
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-2.c
> @@ -0,0 +1,33 @@
> +/* { dg-do compile } */
> +/* { dg-options "-Ofast -fno-finite-math-only -fdump-tree-ifcvt" } */
> +
> +/* Same kernel as ifc-cmp-inverse-1.c.  With NaNs honoured DISTBB >= 0 is
> +   not the inverse of DISTBB < 0, so both comparisons have to stay.  */
> +
> +void
> +f (float *restrict etot, const float *restrict lx, float radij, float 
> r_radij,
> +   float elcdst, float elcdst1, float distdslv, float r_distdslv,
> +   float chrg_init, float dslv_init, int n)
> +{
> +  for (int l = 0; l < n; l++)
> +    {
> +      float distij = lx[l];
> +      float distbb = distij - radij;
> +      int zone1 = distbb < 0.f;
> +
> +      etot[l] += (1.f - distij * r_radij) * (zone1 ? 76.f : 0.f);
> +
> +      float chrg_e = chrg_init
> +       * ((zone1 ? 1.f : 1.f - distbb * elcdst1)
> +          * (distbb < elcdst ? 1.f : 0.f));
> +      etot[l] += chrg_e * 45.f;
> +
> +      float coeff = 1.f - distbb * r_distdslv;
> +      float dslv_e = dslv_init * (distbb < distdslv ? 1.f : 0.f);
> +      dslv_e *= distbb >= 0.f ? coeff : 1.f;
> +      etot[l] += dslv_e;
> +    }
> +}
> +
> +/* { dg-final { scan-tree-dump-times {= \S+ < 0\.0;} 1 "ifcvt" } } */
> +/* { dg-final { scan-tree-dump-times {= \S+ >= 0\.0;} 1 "ifcvt" } } */
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-3.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-3.c
> new file mode 100644
> index 00000000000..022aa52fd26
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-3.c
> @@ -0,0 +1,34 @@
> +/* { dg-do compile } */
> +/* { dg-options "-Ofast -fno-finite-math-only -fdump-tree-ifcvt" } */
> +
> +/* When the source spells the second test as the arms of a select, GCC
> +   builds the else predicate as DISTBB u>= 0, which is the inverse of
> +   DISTBB < 0 even with NaNs honoured, so one comparison is enough.  */
> +
> +void
> +f (float *restrict etot, const float *restrict lx, float radij, float 
> r_radij,
> +   float elcdst, float elcdst1, float distdslv, float r_distdslv,
> +   float chrg_init, float dslv_init, int n)
> +{
> +  for (int l = 0; l < n; l++)
> +    {
> +      float distij = lx[l];
> +      float distbb = distij - radij;
> +      int zone1 = distbb < 0.f;
> +
> +      etot[l] += (1.f - distij * r_radij) * (zone1 ? 76.f : 0.f);
> +
> +      float chrg_e = chrg_init
> +       * ((zone1 ? 1.f : 1.f - distbb * elcdst1)
> +          * (distbb < elcdst ? 1.f : 0.f));
> +      etot[l] += chrg_e * 45.f;
> +
> +      float coeff = 1.f - distbb * r_distdslv;
> +      float dslv_e = dslv_init * (distbb < distdslv ? 1.f : 0.f);
> +      dslv_e *= zone1 ? 1.f : coeff;
> +      etot[l] += dslv_e;
> +    }
> +}
> +
> +/* { dg-final { scan-tree-dump-times {= \S+ < 0\.0;} 1 "ifcvt" } } */
> +/* { dg-final { scan-tree-dump-not {= \S+ u>= 0\.0;} "ifcvt" } } */
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-4.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-4.c
> new file mode 100644
> index 00000000000..867a7381a96
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-4.c
> @@ -0,0 +1,83 @@
> +/* { dg-do run } */
> +/* { dg-options "-Ofast" } */
> +
> +/* Swapping the arms of a select and testing the inverse predicate has to
> +   pick the same value.  Every input and every intermediate value here is a
> +   small integer, so the result does not depend on the order in which the
> +   fast-math flags let the compiler evaluate the expression.  */
> +
> +#define N 41
> +
> +static float lx[N], etot[N], ref[N];
> +
> +__attribute__ ((noipa))
> +static void
> +kernel (float *restrict etot, const float *restrict lx, float radij,
> +       float r_radij, float elcdst, float elcdst1, float distdslv,
> +       float r_distdslv, float chrg_init, float dslv_init, int n)
> +{
> +  for (int l = 0; l < n; l++)
> +    {
> +      float distij = lx[l];
> +      float distbb = distij - radij;
> +      int zone1 = distbb < 0.f;
> +
> +      etot[l] += (1.f - distij * r_radij) * (zone1 ? 76.f : 0.f);
> +
> +      float chrg_e = chrg_init
> +       * ((zone1 ? 1.f : 1.f - distbb * elcdst1)
> +          * (distbb < elcdst ? 1.f : 0.f));
> +      etot[l] += chrg_e * 45.f;
> +
> +      float coeff = 1.f - distbb * r_distdslv;
> +      float dslv_e = dslv_init * (distbb < distdslv ? 1.f : 0.f);
> +      dslv_e *= distbb >= 0.f ? coeff : 1.f;
> +      etot[l] += dslv_e;
> +    }
> +}
> +
> +__attribute__ ((noipa, optimize ("O0")))
> +static void
> +reference (float *restrict etot, const float *restrict lx, float radij,
> +          float r_radij, float elcdst, float elcdst1, float distdslv,
> +          float r_distdslv, float chrg_init, float dslv_init, int n)
> +{
> +  for (int l = 0; l < n; l++)
> +    {
> +      float distij = lx[l];
> +      float distbb = distij - radij;
> +      int zone1 = distbb < 0.f;
> +
> +      etot[l] += (1.f - distij * r_radij) * (zone1 ? 76.f : 0.f);
> +
> +      float chrg_e = chrg_init
> +       * ((zone1 ? 1.f : 1.f - distbb * elcdst1)
> +          * (distbb < elcdst ? 1.f : 0.f));
> +      etot[l] += chrg_e * 45.f;
> +
> +      float coeff = 1.f - distbb * r_distdslv;
> +      float dslv_e = dslv_init * (distbb < distdslv ? 1.f : 0.f);
> +      dslv_e *= distbb >= 0.f ? coeff : 1.f;
> +      etot[l] += dslv_e;
> +    }
> +}
> +
> +int
> +main (void)
> +{
> +  for (int i = 0; i < N; i++)
> +    {
> +      lx[i] = i - N / 2;
> +      etot[i] = i & 3;
> +      ref[i] = etot[i];
> +    }
> +
> +  kernel (etot, lx, 3.f, 2.f, 5.f, 1.f, 4.f, 2.f, 3.f, 2.f, N);
> +  reference (ref, lx, 3.f, 2.f, 5.f, 1.f, 4.f, 2.f, 3.f, 2.f, N);
> +
> +  for (int i = 0; i < N; i++)
> +    if (etot[i] != ref[i])
> +      __builtin_abort ();
> +
> +  return 0;
> +}
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-5.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-5.c
> new file mode 100644
> index 00000000000..2b624516c0f
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-5.c
> @@ -0,0 +1,60 @@
> +/* { dg-do run } */
> +/* { dg-options "-O3 -ffast-math -fno-ssa-phiopt" } */
> +
> +/* Factoring the first select removes a comparison that can be an inverse
> +   predicate for the second select.  */
> +
> +__attribute__ ((noipa))
> +static void
> +kernel (float *restrict out, const float *restrict in, float z, int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    {
> +      float x = in[i * 3];
> +      float y = in[i * 3 + 1];
> +      float w = in[i * 3 + 2];
> +      int p;
> +      if (x < 0)
> +       p = y < z;
> +      else
> +       p = w < z;
> +      out[i * 2] = p;
> +      float q;
> +      if (y >= z)
> +       q = x * 3 + 1;
> +      else
> +       q = x * 2 - 1;
> +      out[i * 2 + 1] = q;
> +    }
> +}
> +
> +__attribute__ ((noipa, optimize ("O0")))
> +static void
> +check (const float *out, const float *in, float z, int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    {
> +      float x = in[i * 3];
> +      float y = in[i * 3 + 1];
> +      float w = in[i * 3 + 2];
> +      float p = x < 0 ? y < z : w < z;
> +      float q = y >= z ? x * 3 + 1 : x * 2 - 1;
> +      if (out[i * 2] != p || out[i * 2 + 1] != q)
> +       __builtin_abort ();
> +    }
> +}
> +
> +int
> +main (void)
> +{
> +  float in[3 * 257], out[2 * 257];
> +  for (int i = 0; i < 3 * 257; ++i)
> +    in[i] = (i * 13 % 41) - 20;
> +  for (int n = 0; n <= 257; ++n)
> +    for (int z = -2; z <= 2; ++z)
> +      {
> +       kernel (out, in, z, n);
> +       check (out, in, z, n);
> +      }
> +  return 0;
> +}
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-6.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-6.c
> new file mode 100644
> index 00000000000..f1431e88d2d
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-6.c
> @@ -0,0 +1,69 @@
> +/* { dg-do run } */
> +/* { dg-options "-O3 -fno-trapping-math -fno-ssa-phiopt" } */
> +/* { dg-require-effective-target flt_dbl_ldbl_inf_nan } */
> +
> +/* Ordered comparisons are not complementary for unordered operands.
> +   Check inverse and reversed comparisons in nested loop predicates.  */
> +
> +#define N 81
> +static float a[N], b[N];
> +static int out[N * 4];
> +
> +__attribute__ ((noipa))
> +static void
> +kernel (int *restrict out, const float *restrict a,
> +        const float *restrict b, int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    {
> +      float x = a[i], y = b[i];
> +      out[4 * i] = x < y ? 11 : 29;
> +      out[4 * i + 1] = x >= y ? 31 : 47;
> +      out[4 * i + 2] = !(x < y) ? 53 : 71;
> +      int r;
> +      if (y <= x)
> +        r = x == y ? 73 : 89;
> +      else
> +        r = x != y ? 97 : 101;
> +      out[4 * i + 3] = r;
> +    }
> +}
> +
> +int
> +main (void)
> +{
> +  float values[9] = { -__builtin_inff (), -2.0f, -0.0f, 0.0f, 2.0f,
> +                     __builtin_inff (), __builtin_nanf (""),
> +                     -__builtin_nanf (""), 1.0f };
> +  /* Equal ranks denote equal values.  Rank 4 denotes unordered values.  */
> +  int rank[9] = { -3, -2, 0, 0, 2, 3, 4, 4, 1 };
> +  for (int i = 0; i < N; ++i)
> +    {
> +      a[i] = values[i / 9];
> +      b[i] = values[i % 9];
> +    }
> +  for (int n = 0; n <= N; ++n)
> +    {
> +      for (int i = 0; i < N * 4; ++i)
> +        out[i] = -1;
> +      kernel (out, a, b, n);
> +      for (int i = 0; i < n; ++i)
> +        {
> +          int x = rank[i / 9], y = rank[i % 9];
> +          int ordered = x != 4 && y != 4;
> +          int lt = ordered && x < y;
> +          int ge = ordered && x >= y;
> +          int eq = ordered && x == y;
> +          if (out[4 * i] != (lt ? 11 : 29)
> +              || out[4 * i + 1] != (ge ? 31 : 47)
> +              || out[4 * i + 2] != (lt ? 71 : 53)
> +              || out[4 * i + 3] != (ge ? (eq ? 73 : 89)
> +                                       : (eq ? 101 : 97)))
> +            __builtin_abort ();
> +        }
> +      for (int i = n * 4; i < N * 4; ++i)
> +        if (out[i] != -1)
> +          __builtin_abort ();
> +    }
> +  return 0;
> +}
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-7.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-7.c
> new file mode 100644
> index 00000000000..7b0b17028f6
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-7.c
> @@ -0,0 +1,57 @@
> +/* { dg-do run } */
> +/* { dg-options "-O3 -fno-ssa-phiopt" } */
> +
> +/* Check reversed integer comparisons and nested predicates at the type
> +   limits.  Each result identifies one path through the conditions.  */
> +
> +#define N 25
> +static int a[N], b[N], out[N * 3];
> +
> +__attribute__ ((noipa))
> +static void
> +kernel (int *restrict out, const int *restrict a,
> +        const int *restrict b, int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    {
> +      int x = a[i], y = b[i];
> +      out[3 * i] = x < y ? 11 : 29;
> +      out[3 * i + 1] = y <= x ? 31 : 47;
> +      int r;
> +      if (x >= y)
> +        r = y == x ? 53 : 71;
> +      else
> +        r = y > 0 ? 73 : 89;
> +      out[3 * i + 2] = r;
> +    }
> +}
> +
> +int
> +main (void)
> +{
> +  int values[5] = { -__INT_MAX__ - 1, -1, 0, 1, __INT_MAX__ };
> +  for (int i = 0; i < N; ++i)
> +    {
> +      a[i] = values[i / 5];
> +      b[i] = values[i % 5];
> +    }
> +  for (int n = 0; n <= N; ++n)
> +    {
> +      for (int i = 0; i < N * 3; ++i)
> +        out[i] = -1;
> +      kernel (out, a, b, n);
> +      for (int i = 0; i < n; ++i)
> +        {
> +          int x = i / 5, y = i % 5;
> +          if (out[3 * i] != (x < y ? 11 : 29)
> +              || out[3 * i + 1] != (x < y ? 47 : 31)
> +              || out[3 * i + 2] != (x >= y ? (x == y ? 53 : 71)
> +                                           : (y > 2 ? 73 : 89)))
> +            __builtin_abort ();
> +        }
> +      for (int i = n * 3; i < N * 3; ++i)
> +        if (out[i] != -1)
> +          __builtin_abort ();
> +    }
> +  return 0;
> +}
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-8.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-8.c
> new file mode 100644
> index 00000000000..cbf12764479
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/ifc-cmp-inverse-8.c
> @@ -0,0 +1,62 @@
> +/* { dg-do run } */
> +/* { dg-options "-O3 -fno-ssa-phiopt" } */
> +
> +/* Check inverse predicates on conditional stores and reductions.
> +   Unsigned arithmetic permits the reductions to wrap.  */
> +
> +#define N 25
> +static unsigned int a[N], b[N], out[N];
> +
> +__attribute__ ((noipa))
> +static unsigned int
> +kernel (unsigned int *restrict out, const unsigned int *restrict a,
> +        const unsigned int *restrict b, int n)
> +{
> +  unsigned int sum = 1;
> +  for (int i = 0; i < n; ++i)
> +    {
> +      unsigned int x = a[i], y = b[i];
> +      if (x < y)
> +        sum += y;
> +      if (y <= x)
> +        {
> +          out[i] = x;
> +          sum -= x;
> +        }
> +    }
> +  return sum;
> +}
> +
> +int
> +main (void)
> +{
> +  unsigned int values[5] = { 0, 1, __INT_MAX__, __INT_MAX__ + 1U, ~0U };
> +  for (int i = 0; i < N; ++i)
> +    {
> +      a[i] = values[i / 5];
> +      b[i] = values[i % 5];
> +    }
> +  for (int n = 0; n <= N; ++n)
> +    {
> +      for (int i = 0; i < N; ++i)
> +        out[i] = 42;
> +      unsigned int sum = kernel (out, a, b, n);
> +      unsigned int expected = 1;
> +      for (int i = 0; i < n; ++i)
> +        {
> +          int x = i / 5, y = i % 5;
> +          if (x < y)
> +            expected += values[y];
> +          else
> +            expected -= values[x];
> +          if (out[i] != (x < y ? 42 : values[x]))
> +            __builtin_abort ();
> +        }
> +      if (sum != expected)
> +        __builtin_abort ();
> +      for (int i = n; i < N; ++i)
> +        if (out[i] != 42)
> +          __builtin_abort ();
> +    }
> +  return 0;
> +}
> diff --git a/gcc/tree-ssa-sccvn.cc b/gcc/tree-ssa-sccvn.cc
> index c496ff93d11..d9ac15708b2 100644
> --- a/gcc/tree-ssa-sccvn.cc
> +++ b/gcc/tree-ssa-sccvn.cc
> @@ -5709,6 +5709,79 @@ visit_nary_op (tree lhs, gassign *stmt)
>    tree rhs1 = gimple_assign_rhs1 (stmt);
>    switch (code)
>      {
> +    case COND_EXPR:
> +      {
> +       /* Valueization can return a constant even for an SSA selector.  */
> +       tree cond = vn_valueize (rhs1);
> +       gassign *cmp = NULL;
> +       if (TREE_CODE (cond) == SSA_NAME)
> +         cmp = dyn_cast <gassign *> (SSA_NAME_DEF_STMT (cond));
> +       if (!cmp
> +           || TREE_CODE_CLASS (gimple_assign_rhs_code (cmp)) != 
> tcc_comparison
> +           /* invert_tree_comparison allows EQ/NE inversion even when 
> signaling
> +              NaNs can trap.  It preserves the trap at the comparison, but
> +              reusing a later comparison must not move an earlier trap past
> +              a side effect.  */
> +           || gimple_could_trap_p (cmp))
> +         break;
> +       tree_code cmp_code = gimple_assign_rhs_code (cmp);
> +       tree ops[2] = { gimple_assign_rhs1 (cmp), gimple_assign_rhs2 (cmp) };
> +       tree_code inverse_code
> +         = invert_tree_comparison (cmp_code, HONOR_NANS (ops[0]));
> +       if (inverse_code == ERROR_MARK)
> +         break;
> +       tree inverse = vn_nary_op_lookup_pieces (2, inverse_code,
> +                                              TREE_TYPE (cond), ops, NULL);
> +       if (!inverse)
> +         break;
> +       tree select_ops[3] = { inverse, gimple_assign_rhs3 (stmt),
> +                             gimple_assign_rhs2 (stmt) };
> +       /* Equivalent selects can be found in either direction.  */
> +       result = vn_nary_op_lookup_pieces (3, COND_EXPR, type,
> +                                        select_ops, NULL);
> +       /* Create new selects in only one direction, using the lower 
> comparison
> +          code.  A mere inequality would let each polarity replace the other.
> +          The inverse lookup can return a constant, not just an SSA name.  */

What does lower comparison code mean?  I don't think the comparison
codes are in any official order at this point.  This is why for an
example combine_comparisons does its own translation to something
which does have an order and meaning.  And even why
convert_tree_comp_to_rtx (besides the unsignedness) have a switch
statement.
This comment here needs to be expanded more.

Actually better yet, why not create a canonicalization form rather
than what you are doing?  That is for NE, always look up the EQ
version.  For LT, always look up the GE.  Etc.

> +       if (!result && inverse_code < cmp_code
> +           && TREE_CODE (inverse) == SSA_NAME)
> +         {
> +           /* An SSA name can have a constant value number or VN_TOP.  */
> +           tree value = SSA_VAL (inverse);
> +           vn_avail *av = (TREE_CODE (value) == SSA_NAME
> +                           ? VN_INFO (value)->avail : NULL);
> +           /* Require an inverse available in this block.  Checking the 
> latest
> +              availability record keeps eliminate_avail on its same-block
> +              fast path, without walking the availability chain.  */
> +           tree leader = NULL_TREE;
> +           if (rpo_avail && av && av->location == gimple_bb (stmt)->index)
> +             leader = rpo_avail->eliminate_avail (gimple_bb (stmt), inverse);
> +           gassign *def = NULL;
> +           if (leader && TREE_CODE (leader) == SSA_NAME)
> +             def = dyn_cast <gassign *> (SSA_NAME_DEF_STMT (leader));
> +           /* The available leader need not be the comparison returned by the
> +              lookup.  Require a comparison in this block and check its code
> +              too: VN can swap operands and thus change the comparison code.
> +              The strict ordering must also hold for this actual leader.  */
> +           if (def && gimple_bb (def) == gimple_bb (stmt)
> +               && TREE_CODE_CLASS (gimple_assign_rhs_code (def))
> +                  == tcc_comparison
> +               && gimple_assign_rhs_code (def) < cmp_code)
> +             {
> +               gimple_match_op match_op (gimple_match_cond::UNCOND,
> +                                        COND_EXPR, type, leader,
> +                                        select_ops[1], select_ops[2]);
> +               result = vn_nary_build_or_lookup (&match_op);
> +             }
> +         }
> +       if (result)
> +         {
> +           bool changed = set_ssa_val_to (lhs, result);
> +           if (TREE_CODE (result) == SSA_NAME)
> +             vn_nary_op_insert_stmt (stmt, result);
> +           return changed;
> +         }
> +      }
> +      break;
>      CASE_CONVERT:
>        /* Match arithmetic done in a different type where we can easily
>           substitute the result from some earlier sign-changed or widened
> @@ -7203,6 +7276,7 @@ eliminate_dom_walker::eliminate_insert (basic_block bb,
>        || (!CONVERT_EXPR_CODE_P (gimple_assign_rhs_code (stmt))
>           && gimple_assign_rhs_code (stmt) != VIEW_CONVERT_EXPR
>           && gimple_assign_rhs_code (stmt) != NEGATE_EXPR
> +         && gimple_assign_rhs_code (stmt) != COND_EXPR
>           && gimple_assign_rhs_code (stmt) != BIT_FIELD_REF
>           && (gimple_assign_rhs_code (stmt) != BIT_AND_EXPR
>               || TREE_CODE (gimple_assign_rhs2 (stmt)) != INTEGER_CST)))
> @@ -7226,6 +7300,19 @@ eliminate_dom_walker::eliminate_insert (basic_block bb,
>    else if (gimple_assign_rhs_code (stmt) == BIT_AND_EXPR)
>      res = gimple_build (&stmts, BIT_AND_EXPR,
>                         TREE_TYPE (val), leader, gimple_assign_rhs2 (stmt));
> +  else if (gimple_assign_rhs_code (stmt) == COND_EXPR)
> +    {
> +      tree arg1 = gimple_assign_rhs2 (stmt);
> +      tree arg2 = gimple_assign_rhs3 (stmt);
> +      if (TREE_CODE (arg1) == SSA_NAME)
> +       arg1 = eliminate_avail (bb, arg1);
> +      if (TREE_CODE (arg2) == SSA_NAME)
> +       arg2 = eliminate_avail (bb, arg2);
> +      if (!arg1 || !arg2)
> +       return NULL_TREE;
> +      res = gimple_build (&stmts, COND_EXPR, TREE_TYPE (val),
> +                        leader, arg1, arg2);
> +    }
>    else
>      res = gimple_build (&stmts, gimple_assign_rhs_code (stmt),
>                         TREE_TYPE (val), leader);
> --
> 2.50.1 (Apple Git-155)
>

Reply via email to