[PATCH] middle-end: fix de-optimizations with bitclear patterns on signed values

Jakub Jelinek jakub@redhat.com
Thu Nov 4 15:10:33 GMT 2021


On Thu, Nov 04, 2021 at 12:19:34PM +0000, Tamar Christina wrote:
> I'm not sure the precision matters since if the conversion resulted in not enough
> precision such that It influences the compare it would have been optimized out.

You can't really rely on other optimizations being performed.  They will
usually happen, but might not because such code only materialized short time
ago without folding happening in between, or some debug counters or -fno-*
disabling some passes, ...

> --- a/gcc/tree-ssa-phiopt.c
> +++ b/gcc/tree-ssa-phiopt.c
> @@ -2038,6 +2038,34 @@ spaceship_replacement (basic_block cond_bb, basic_block middle_bb,
>    gimple *orig_use_stmt = use_stmt;
>    tree orig_use_lhs = NULL_TREE;
>    int prec = TYPE_PRECISION (TREE_TYPE (phires));
> +  bool is_cast = false;
> +
> +  /* Deal with the case when match.pd has rewritten the (res & ~1) == 0
> +     into res <= 1 and has left a type-cast for signed types.  */
> +  if (gimple_assign_cast_p (use_stmt))
> +    {
> +      orig_use_lhs = gimple_assign_lhs (use_stmt);
> +      /* match.pd would have only done this for a signed type,
> +	 so the conversion must be to an unsigned one.  */
> +      tree ty1 = TREE_TYPE (gimple_assign_rhs1 (use_stmt));
> +      tree ty2 = TREE_TYPE (orig_use_lhs);

gimple_assign_rhs1 (use_stmt) is I think guaranteed to be phires
here.  And that has some of this checked already at the start of
the function:
  if (!INTEGRAL_TYPE_P (TREE_TYPE (phires))
      || TYPE_UNSIGNED (TREE_TYPE (phires))

> +
> +      if (TYPE_UNSIGNED (ty1) || !INTEGRAL_TYPE_P (ty1))
> +	return false;

So I think the above two lines are redundant.

> +      if (!TYPE_UNSIGNED (ty2) || !INTEGRAL_TYPE_P (ty2))
> +	return false;
> +      if (TYPE_PRECISION (ty1) != TYPE_PRECISION (ty2))
> +	return false;
> +      if (SSA_NAME_OCCURS_IN_ABNORMAL_PHI (orig_use_lhs))
> +	return false;
> +      if (EDGE_COUNT (phi_bb->preds) != 4)
> +	return false;
> +      if (!single_imm_use (orig_use_lhs, &use_p, &use_stmt))
> +	return false;
> +
> +      is_cast = true;
> +    }
> +
>    if (is_gimple_assign (use_stmt)

I'd feel much safer if this was else if rather than if.
The reason for the patch is that (res & ~1) == 0 is optimized
into (unsigned) res <= 1, right, so it can be either this or that
and you don't need both.  If you want to also handle both, that would
mean figuring all the details even for that case, handling of debug stmts
etc.

>        && gimple_assign_rhs_code (use_stmt) == BIT_AND_EXPR
>        && TREE_CODE (gimple_assign_rhs2 (use_stmt)) == INTEGER_CST
> @@ -2099,7 +2127,7 @@ spaceship_replacement (basic_block cond_bb, basic_block middle_bb,
>        || !tree_fits_shwi_p (rhs)
>        || !IN_RANGE (tree_to_shwi (rhs), -1, 1))
>      return false;
> -  if (orig_use_lhs)
> +  if (orig_use_lhs && !is_cast)

Because otherwise it is unclear what the above means, the
intent is that the if handles the case where BIT_AND_EXPR is present,
but with both cast to unsigned and BIT_AND_EXPR present it acts differently.

> @@ -2345,6 +2373,8 @@ spaceship_replacement (basic_block cond_bb, basic_block middle_bb,
>  	res_cmp = one_cmp == LT_EXPR ? GE_EXPR : LE_EXPR;
>        else if (integer_minus_onep (rhs))
>  	res_cmp = one_cmp == LT_EXPR ? GT_EXPR : LT_EXPR;
> +      else if (integer_onep (rhs) && is_cast)
> +	res_cmp = GE_EXPR;
>        else
>  	return false;
>        break;
> @@ -2353,6 +2383,8 @@ spaceship_replacement (basic_block cond_bb, basic_block middle_bb,
>  	res_cmp = one_cmp == LT_EXPR ? LE_EXPR : GE_EXPR;
>        else if (integer_zerop (rhs))
>  	res_cmp = one_cmp;
> +      else if (integer_onep (rhs) && is_cast)
> +	res_cmp = LE_EXPR;
>        else
>  	return false;
>        break;

I'm afraid this is still wrong.  Because is_cast which implies
that the comparison is done in unsigned type rather than signed type
which is otherwise ensured changes everything the code assumes.
While maybe EQ_EXPR and NE_EXPR will work the same whether it is unsigned or
signed comparison, the other comparisons certainly will not.

So, my preference would be instead of doing these 2 hunks handle the is_cast
case early, right before if (orig_use_lhs) above.  Something like:
  if (is_cast)
    {
      if (TREE_CODE (rhs) != INTEGER_CST)
	return false;
      /* As for -ffast-math we assume the 2 return to be
 	 impossible, canonicalize (unsigned) res <= 1U or
	 (unsigned) res < 2U into res >= 0 and (unsigned) res > 1U
	 or (unsigned) res >= 2U as res < 0.  */
      switch (cmp)
	{
	case LE_EXPR:
	  if (!integer_onep (rhs))
	    return false;
	  cmp = GE_EXPR;
	  break;
	case LT_EXPR:
	  if (wi::ne_p (wi::to_widest (rhs), 2))
	    return false;
	  cmp = GE_EXPR;
	  break;
	case GT_EXPR:
	  if (!integer_onep (rhs))
	    return false;
	  cmp = LT_EXPR;
	  break;
	case GE_EXPR:
	  if (wi::ne_p (wi::to_widest (rhs), 2))
	    return false;
	  cmp = LT_EXPR;
	  break;
	default:
	  return false;
	}
      rhs = build_zero_cst (TREE_TYPE (phires));
    }
  else if (orig_use_lhs)
    {
...
Similarly to the BIT_AND_EXPR case the above transforms
the is_cast case into a signed comparison that the later code
knows how to handle.

There is another problem though.  If there are debug stmts, the
code later on does:
          /* If there are debug uses, emit something like:
             # DEBUG D#1 => i_2(D) > j_3(D) ? 1 : -1
             # DEBUG D#2 => i_2(D) == j_3(D) ? 0 : D#1
             where > stands for the comparison that yielded 1
             and replace debug uses of phi result with that D#2.
             Ignore the value of 2, because if NaNs aren't expected,
             all floating point numbers should be comparable.  */
...
          replace_uses_by (phires, temp2);
          if (orig_use_lhs)
            replace_uses_by (orig_use_lhs, temp2);
For the is_cast case this creates invalid IL, because the uses of
orig_use_lhs if any expect an unsigned value, but temp2 is signed.
Perhaps when one uses <compare> stuff this will never happen, but
there is nothing that prevents a user to write his own code so that
spaceship_replacement actually triggers on it (i.e. results in the
same IL) and has debug info uses on it.  And we can't e.g. punt if
there are debug stmts and not otherwise because that would result
in -fcompare-debug differences.
So, I think we need:
       bool has_debug_uses = false;
+      bool has_cast_debug_uses = false;
...
-          if (!has_debug_uses)
+          if (!has_debug_uses || is_cast)
             FOR_EACH_IMM_USE_FAST (use_p, iter, orig_use_lhs)
               {
                 gimple *use_stmt = USE_STMT (use_p);
                 gcc_assert (is_gimple_debug (use_stmt));
                 has_debug_uses = true;
+                if (is_cast)
+                  has_cast_debug_uses = true;
              }
and later if (has_cast_debug_uses) create another debug temporary
temp3 with TREE_TYPE set to TREE_TYPE (orig_use_lhs),
the expression (that_uns_type) temp2 and use that temp3 instead of
temp2 in replace_uses_by (orig_use_lhs, temp2);

	Jakub



More information about the Gcc-patches mailing list