range-op-float: Improve binary reverse operations

Message ID Y44PYa8n3RkNIfCn@tucnak
State Unresolved
Headers
Series range-op-float: Improve binary reverse operations |

Checks

Context Check Description
snail/gcc-patch-check warning Git am fail log

Commit Message

Jakub Jelinek Dec. 5, 2022, 3:33 p.m. UTC
  Hi!

On Mon, Dec 05, 2022 at 02:29:36PM +0100, Aldy Hernandez wrote:
> > So like this for multiplication op1/2_range if it passes bootstrap/regtest?
> > For division I'll need to go to a drawing board...
> 
> Sure, looks good to me.

Ulrich just filed PR107972, so in the light of that PR the following patch
attempts to do that differently.

Is this also ok if it passes bootstrap/regtest?

As for testcase, I've tried both attached testcases, but unfortunately it
seems that in neither of the cases we actually figure out that res range
is finite (or for last function non-zero ordered).  So there is further
work needed on that.

2022-12-05  Jakub Jelinek  <jakub@redhat.com>

	PR tree-optimization/107972
	* range-op-float.cc (frange_drop_infs): New function.
	(float_binary_op_range_finish): Add OP argument.  If OP is not
	RDIV_EXPR and lhs is finite, r must be finite too.
	(foperator_plus::op1_range, foperator_minus::op1_range,
	foperator_minus::op2_range, foperator_mult::op1_range): Pass
	operation code to float_binary_op_range_finish.
	(foperator_div::op1_range): Pass RDIV_EXPR to
	float_binary_op_range_finish.  If lhs is finite, r must be finite
	too.
	(foperator_div::op2_range): Pass RDIV_EXPR to
	float_binary_op_range_finish.  If lhs is not NAN nor zero, r must
	be finite.



	Jakub
double
foo (double a, double b)
{
  if (!__builtin_isfinite (a))
    return 42.0;
  if (!__builtin_isfinite (b))
    return 42.0;
  double res = a + b;
  if (!__builtin_isfinite (res))
    __builtin_unreachable ();
  return res;
}

double
bar (double a, double b)
{
  if (!__builtin_isfinite (a))
    return 42.0;
  if (!__builtin_isfinite (b))
    return 42.0;
  double res = a - b;
  if (!__builtin_isfinite (res))
    __builtin_unreachable ();
  return res;
}

double
baz (double a, double b)
{
  if (!__builtin_isfinite (a))
    return 42.0;
  if (!__builtin_isfinite (b))
    return 42.0;
  double res = a * b;
  if (!__builtin_isfinite (res))
    __builtin_unreachable ();
  return res;
}

double
qux (double a, double b)
{
  if (!__builtin_isfinite (a))
    return 42.0;
  double res = a / b;
  if (!__builtin_isfinite (res))
    __builtin_unreachable ();
  return res;
}

double
quux (double a, double b)
{
  if (!__builtin_isfinite (b))
    return 42.0;
  double res = a  / b;
  if (__builtin_isnan (res) || res == 0.0)
    __builtin_unreachable ();
  return res;
}
double
foo (double a, double b)
{
  if (!__builtin_isfinite (a))
    return 42.0;
  if (!__builtin_isfinite (b))
    return 42.0;
  double res = a + b;
  __attribute__((assume (__builtin_isfinite (res))));
  return res;
}

double
bar (double a, double b)
{
  if (!__builtin_isfinite (a))
    return 42.0;
  if (!__builtin_isfinite (b))
    return 42.0;
  double res = a - b;
  __attribute__((assume (__builtin_isfinite (res))));
  return res;
}

double
baz (double a, double b)
{
  if (!__builtin_isfinite (a))
    return 42.0;
  if (!__builtin_isfinite (b))
    return 42.0;
  double res = a * b;
  __attribute__((assume (__builtin_isfinite (res))));
  return res;
}

double
qux (double a, double b)
{
  if (!__builtin_isfinite (a))
    return 42.0;
  double res = a / b;
  __attribute__((assume (__builtin_isfinite (res))));
  return res;
}

double
quux (double a, double b)
{
  if (!__builtin_isfinite (b))
    return 42.0;
  double res = a  / b;
  __attribute__((assume (!__builtin_isnan (res) && res != 0.0)));
  return res;
}
  

Comments

Andrew MacLeod Dec. 5, 2022, 8:43 p.m. UTC | #1
On 12/5/22 10:33, Jakub Jelinek wrote:
> Hi!
>
> On Mon, Dec 05, 2022 at 02:29:36PM +0100, Aldy Hernandez wrote:
>>> So like this for multiplication op1/2_range if it passes bootstrap/regtest?
>>> For division I'll need to go to a drawing board...
>> Sure, looks good to me.
> Ulrich just filed PR107972, so in the light of that PR the following patch
> attempts to do that differently.
>
> Is this also ok if it passes bootstrap/regtest?


Id actually prefer to avoid passing the tree code around... we're trying 
to avoid that sort of thing even though Aldy temporarily introduced them 
to range-ops. Hes suppose to remove that next stage 1 :-P   Ideally 
anything "special" is locally contained to the specific routine.

It looks like the only time we actually do anything different is for 
divide op2_range?   the divide op1_range seems to still call 
frange_drop_infs() under the same conditions..

In which case, maybe we can just change op2_range to contain all the 
special casing.. frange_drop_infs doesnt seem overly complicated, so 
just specialize it?  ie for divide op2_range do something like this 
instead of the call?:

if (!ret)
   return false;
if (r.known_isnan () || lhs.known_isnan () || r.undefined_p ())
   r.set_varying (type);
else if (!lhs.maybe_isnan ())
   {
     r.clear_nan();
     if (!contains_zero_p (lhs_lb, lhs_ub))
       frange_drop_infs (r, type);
   }
else
   r.update_nan ();
return true;


or whatever the sequence precisely works out to.

Andrew


> As for testcase, I've tried both attached testcases, but unfortunately it
> seems that in neither of the cases we actually figure out that res range
> is finite (or for last function non-zero ordered).  So there is further
> work needed on that.
>
> 2022-12-05  Jakub Jelinek  <jakub@redhat.com>
>
> 	PR tree-optimization/107972
> 	* range-op-float.cc (frange_drop_infs): New function.
> 	(float_binary_op_range_finish): Add OP argument.  If OP is not
> 	RDIV_EXPR and lhs is finite, r must be finite too.
> 	(foperator_plus::op1_range, foperator_minus::op1_range,
> 	foperator_minus::op2_range, foperator_mult::op1_range): Pass
> 	operation code to float_binary_op_range_finish.
> 	(foperator_div::op1_range): Pass RDIV_EXPR to
> 	float_binary_op_range_finish.  If lhs is finite, r must be finite
> 	too.
> 	(foperator_div::op2_range): Pass RDIV_EXPR to
> 	float_binary_op_range_finish.  If lhs is not NAN nor zero, r must
> 	be finite.
>
> --- gcc/range-op-float.cc.jj	2022-12-05 11:17:34.900573272 +0100
> +++ gcc/range-op-float.cc	2022-12-05 16:13:54.414845672 +0100
> @@ -330,6 +330,18 @@ frange_drop_ninf (frange &r, tree type)
>     r.intersect (tmp);
>   }
>   
> +// Crop R to [MIN, MAX] where MAX is the maximum representable number
> +// for TYPE and MIN the minimum representable number for TYPE.
> +
> +static inline void
> +frange_drop_infs (frange &r, tree type)
> +{
> +  REAL_VALUE_TYPE max = real_max_representable (type);
> +  REAL_VALUE_TYPE min = real_min_representable (type);
> +  frange tmp (type, min, max);
> +  r.intersect (tmp);
> +}
> +
>   // If zero is in R, make sure both -0.0 and +0.0 are in the range.
>   
>   static inline void
> @@ -1883,7 +1895,7 @@ foperator_unordered_equal::op1_range (fr
>   
>   static bool
>   float_binary_op_range_finish (bool ret, frange &r, tree type,
> -			      const frange &lhs)
> +			      const frange &lhs, enum tree_code op)
>   {
>     if (!ret)
>       return false;
> @@ -1904,7 +1916,16 @@ float_binary_op_range_finish (bool ret,
>     // If lhs isn't NAN, then neither operand could be NAN,
>     // even if the reverse operation does introduce a maybe_nan.
>     if (!lhs.maybe_isnan ())
> -    r.clear_nan ();
> +    {
> +      r.clear_nan ();
> +      if (op != RDIV_EXPR
> +	  && !real_isinf (&lhs.lower_bound ())
> +	  && !real_isinf (&lhs.upper_bound ()))
> +	// For reverse + or - or *, if result is finite, then r must
> +	// be finite too, as X + INF or X - INF or X * INF is always
> +	// +-INF or NAN.  For division handle it in the caller.
> +	frange_drop_infs (r, type);
> +    }
>     // If lhs is a maybe or known NAN, the operand could be
>     // NAN.
>     else
> @@ -2020,7 +2041,7 @@ public:
>       if (!minus)
>         return false;
>       return float_binary_op_range_finish (minus.fold_range (r, type, lhs, op2),
> -					 r, type, lhs);
> +					 r, type, lhs, PLUS_EXPR);
>     }
>     virtual bool op2_range (frange &r, tree type,
>   			  const frange &lhs,
> @@ -2067,7 +2088,7 @@ public:
>         return false;
>       return float_binary_op_range_finish (fop_plus.fold_range (r, type, lhs,
>   							      op2),
> -					 r, type, lhs);
> +					 r, type, lhs, MINUS_EXPR);
>     }
>     virtual bool op2_range (frange &r, tree type,
>   			  const frange &lhs,
> @@ -2077,7 +2098,7 @@ public:
>       if (lhs.undefined_p ())
>         return false;
>       return float_binary_op_range_finish (fold_range (r, type, op1, lhs),
> -					 r, type, lhs);
> +					 r, type, lhs, MINUS_EXPR);
>     }
>   private:
>     void rv_fold (REAL_VALUE_TYPE &lb, REAL_VALUE_TYPE &ub, bool &maybe_nan,
> @@ -2166,7 +2187,7 @@ public:
>       // or if lhs must be zero and op2 doesn't include zero, it would be
>       // UNDEFINED, while rdiv.fold_range computes a zero or singleton INF
>       // range.  Those are supersets of UNDEFINED, so let's keep that way.
> -    return float_binary_op_range_finish (ret, r, type, lhs);
> +    return float_binary_op_range_finish (ret, r, type, lhs, MULT_EXPR);
>     }
>     virtual bool op2_range (frange &r, tree type,
>   			  const frange &lhs,
> @@ -2313,7 +2334,12 @@ public:
>   	zero_to_inf_range (lb, ub, signbit_known);
>   	r.set (type, lb, ub);
>         }
> -    return float_binary_op_range_finish (ret, r, type, lhs);
> +    // If lhs must be finite (can't be +-INF nor NAN), then op1 must be
> +    // finite too - +-INF / anything is either +-INF or NAN (NAN if op2 is
> +    // +-INF or NAN).
> +    if (!real_isinf (&lhs_lb) && !real_isinf (&lhs_ub) && !lhs.maybe_isnan ())
> +      frange_drop_infs (r, type);
> +    return float_binary_op_range_finish (ret, r, type, lhs, RDIV_EXPR);
>     }
>     virtual bool op2_range (frange &r, tree type,
>   			  const frange &lhs,
> @@ -2341,7 +2367,11 @@ public:
>   	zero_to_inf_range (lb, ub, signbit_known);
>   	r.set (type, lb, ub);
>         }
> -    return float_binary_op_range_finish (ret, r, type, lhs);
> +    // If lhs can't be zero nor NAN, then op2 must be finite - anything / +-INF
> +    // is either +-0 or NAN (NAN if op1 is +-INF or NAN).
> +    if (!contains_zero_p (lhs_lb, lhs_ub) && !lhs.maybe_isnan ())
> +      frange_drop_infs (r, type);
> +    return float_binary_op_range_finish (ret, r, type, lhs, RDIV_EXPR);
>     }
>   private:
>     void rv_fold (REAL_VALUE_TYPE &lb, REAL_VALUE_TYPE &ub, bool &maybe_nan,
>
>
> 	Jakub
  
Jakub Jelinek Dec. 5, 2022, 8:54 p.m. UTC | #2
On Mon, Dec 05, 2022 at 03:43:16PM -0500, Andrew MacLeod wrote:
> Id actually prefer to avoid passing the tree code around... we're trying to
> avoid that sort of thing even though Aldy temporarily introduced them to
> range-ops. Hes suppose to remove that next stage 1 :-P   Ideally anything
> "special" is locally contained to the specific routine.

Would a bool divide_op2 argument be better (perhaps defaulted to false)?
Inlining float_binary_op_range_finish by hand doesn't seem to be a good
idea, if it needs to be changed, it would need to be changed in multiple
places...

	Jakub
  
Jakub Jelinek Dec. 5, 2022, 10:38 p.m. UTC | #3
On Mon, Dec 05, 2022 at 09:54:09PM +0100, Jakub Jelinek via Gcc-patches wrote:
> On Mon, Dec 05, 2022 at 03:43:16PM -0500, Andrew MacLeod wrote:
> > Id actually prefer to avoid passing the tree code around... we're trying to
> > avoid that sort of thing even though Aldy temporarily introduced them to
> > range-ops. Hes suppose to remove that next stage 1 :-P   Ideally anything
> > "special" is locally contained to the specific routine.
> 
> Would a bool divide_op2 argument be better (perhaps defaulted to false)?
> Inlining float_binary_op_range_finish by hand doesn't seem to be a good
> idea, if it needs to be changed, it would need to be changed in multiple
> places...

In patch form on top of PR107975 patch.

2022-12-05  Jakub Jelinek  <jakub@redhat.com>

	PR tree-optimization/107972
	* range-op-float.cc (frange_drop_infs): New function.
	(float_binary_op_range_finish): Add DIV_OP2 argument.  If DIV_OP2 is
	false and lhs is finite or if DIV_OP2 is true and lhs is non-zero and
	not NAN, r must be finite too.
	(foperator_div::op2_range): Pass true to DIV_OP2 of
	float_binary_op_range_finish.

--- gcc/range-op-float.cc.jj	2022-12-05 11:17:34.900573272 +0100
+++ gcc/range-op-float.cc	2022-12-05 16:13:54.414845672 +0100
@@ -330,6 +330,18 @@ frange_drop_ninf (frange &r, tree type)
   r.intersect (tmp);
 }
 
+// Crop R to [MIN, MAX] where MAX is the maximum representable number
+// for TYPE and MIN the minimum representable number for TYPE.
+
+static inline void
+frange_drop_infs (frange &r, tree type)
+{
+  REAL_VALUE_TYPE max = real_max_representable (type);
+  REAL_VALUE_TYPE min = real_min_representable (type);
+  frange tmp (type, min, max);
+  r.intersect (tmp);
+}
+
 // If zero is in R, make sure both -0.0 and +0.0 are in the range.
 
 static inline void
@@ -1883,7 +1895,7 @@ foperator_unordered_equal::op1_range (fr
 
 static bool
 float_binary_op_range_finish (bool ret, frange &r, tree type,
-			      const frange &lhs)
+			      const frange &lhs, bool div_op2 = false)
 {
   if (!ret)
     return false;
@@ -1904,7 +1916,20 @@ float_binary_op_range_finish (bool ret,
   // If lhs isn't NAN, then neither operand could be NAN,
   // even if the reverse operation does introduce a maybe_nan.
   if (!lhs.maybe_isnan ())
-    r.clear_nan ();
+    {
+      r.clear_nan ();
+      if (div_op2
+	  ? !(real_compare (LE_EXPR, &lhs.lower_bound (), &dconst0)
+	      && real_compare (GE_EXPR, &lhs.upper_bound (), &dconst0))
+	  : !(real_isinf (&lhs.lower_bound ())
+	      || real_isinf (&lhs.upper_bound ())))
+	// For reverse + or - or * or op1 of /, if result is finite, then
+	// r must be finite too, as X + INF or X - INF or X * INF or
+	// INF / X is always +-INF or NAN.  For op2 of /, if result is
+	// non-zero and not NAN, r must be finite, as X / INF is always
+	// 0 or NAN.
+	frange_drop_infs (r, type);
+    }
   // If lhs is a maybe or known NAN, the operand could be
   // NAN.
   else
@@ -2330,7 +2355,7 @@ public:
     if (!ret)
       return ret;
     if (lhs.known_isnan () || op1.known_isnan () || op1.undefined_p ())
-      return float_binary_op_range_finish (ret, r, type, lhs);
+      return float_binary_op_range_finish (ret, r, type, lhs, true);
     const REAL_VALUE_TYPE &lhs_lb = lhs.lower_bound ();
     const REAL_VALUE_TYPE &lhs_ub = lhs.upper_bound ();
     const REAL_VALUE_TYPE &op1_lb = op1.lower_bound ();
@@ -2347,7 +2372,7 @@ public:
 	zero_to_inf_range (lb, ub, signbit_known);
 	r.set (type, lb, ub);
       }
-    return float_binary_op_range_finish (ret, r, type, lhs);
+    return float_binary_op_range_finish (ret, r, type, lhs, true);
   }
 private:
   void rv_fold (REAL_VALUE_TYPE &lb, REAL_VALUE_TYPE &ub, bool &maybe_nan,


	Jakub
  
Andrew MacLeod Dec. 5, 2022, 11:49 p.m. UTC | #4
On 12/5/22 17:38, Jakub Jelinek wrote:
> On Mon, Dec 05, 2022 at 09:54:09PM +0100, Jakub Jelinek via Gcc-patches wrote:
>> On Mon, Dec 05, 2022 at 03:43:16PM -0500, Andrew MacLeod wrote:
>>> Id actually prefer to avoid passing the tree code around... we're trying to
>>> avoid that sort of thing even though Aldy temporarily introduced them to
>>> range-ops. Hes suppose to remove that next stage 1 :-P   Ideally anything
>>> "special" is locally contained to the specific routine.
>> Would a bool divide_op2 argument be better (perhaps defaulted to false)?
>> Inlining float_binary_op_range_finish by hand doesn't seem to be a good
>> idea, if it needs to be changed, it would need to be changed in multiple
>> places...

Yeah thats also fine.

Andrew


> In patch form on top of PR107975 patch.
>
> 2022-12-05  Jakub Jelinek  <jakub@redhat.com>
>
> 	PR tree-optimization/107972
> 	* range-op-float.cc (frange_drop_infs): New function.
> 	(float_binary_op_range_finish): Add DIV_OP2 argument.  If DIV_OP2 is
> 	false and lhs is finite or if DIV_OP2 is true and lhs is non-zero and
> 	not NAN, r must be finite too.
> 	(foperator_div::op2_range): Pass true to DIV_OP2 of
> 	float_binary_op_range_finish.
>
> --- gcc/range-op-float.cc.jj	2022-12-05 11:17:34.900573272 +0100
> +++ gcc/range-op-float.cc	2022-12-05 16:13:54.414845672 +0100
> @@ -330,6 +330,18 @@ frange_drop_ninf (frange &r, tree type)
>     r.intersect (tmp);
>   }
>   
> +// Crop R to [MIN, MAX] where MAX is the maximum representable number
> +// for TYPE and MIN the minimum representable number for TYPE.
> +
> +static inline void
> +frange_drop_infs (frange &r, tree type)
> +{
> +  REAL_VALUE_TYPE max = real_max_representable (type);
> +  REAL_VALUE_TYPE min = real_min_representable (type);
> +  frange tmp (type, min, max);
> +  r.intersect (tmp);
> +}
> +
>   // If zero is in R, make sure both -0.0 and +0.0 are in the range.
>   
>   static inline void
> @@ -1883,7 +1895,7 @@ foperator_unordered_equal::op1_range (fr
>   
>   static bool
>   float_binary_op_range_finish (bool ret, frange &r, tree type,
> -			      const frange &lhs)
> +			      const frange &lhs, bool div_op2 = false)
>   {
>     if (!ret)
>       return false;
> @@ -1904,7 +1916,20 @@ float_binary_op_range_finish (bool ret,
>     // If lhs isn't NAN, then neither operand could be NAN,
>     // even if the reverse operation does introduce a maybe_nan.
>     if (!lhs.maybe_isnan ())
> -    r.clear_nan ();
> +    {
> +      r.clear_nan ();
> +      if (div_op2
> +	  ? !(real_compare (LE_EXPR, &lhs.lower_bound (), &dconst0)
> +	      && real_compare (GE_EXPR, &lhs.upper_bound (), &dconst0))
> +	  : !(real_isinf (&lhs.lower_bound ())
> +	      || real_isinf (&lhs.upper_bound ())))
> +	// For reverse + or - or * or op1 of /, if result is finite, then
> +	// r must be finite too, as X + INF or X - INF or X * INF or
> +	// INF / X is always +-INF or NAN.  For op2 of /, if result is
> +	// non-zero and not NAN, r must be finite, as X / INF is always
> +	// 0 or NAN.
> +	frange_drop_infs (r, type);
> +    }
>     // If lhs is a maybe or known NAN, the operand could be
>     // NAN.
>     else
> @@ -2330,7 +2355,7 @@ public:
>       if (!ret)
>         return ret;
>       if (lhs.known_isnan () || op1.known_isnan () || op1.undefined_p ())
> -      return float_binary_op_range_finish (ret, r, type, lhs);
> +      return float_binary_op_range_finish (ret, r, type, lhs, true);
>       const REAL_VALUE_TYPE &lhs_lb = lhs.lower_bound ();
>       const REAL_VALUE_TYPE &lhs_ub = lhs.upper_bound ();
>       const REAL_VALUE_TYPE &op1_lb = op1.lower_bound ();
> @@ -2347,7 +2372,7 @@ public:
>   	zero_to_inf_range (lb, ub, signbit_known);
>   	r.set (type, lb, ub);
>         }
> -    return float_binary_op_range_finish (ret, r, type, lhs);
> +    return float_binary_op_range_finish (ret, r, type, lhs, true);
>     }
>   private:
>     void rv_fold (REAL_VALUE_TYPE &lb, REAL_VALUE_TYPE &ub, bool &maybe_nan,
>
>
> 	Jakub
>
  

Patch

--- gcc/range-op-float.cc.jj	2022-12-05 11:17:34.900573272 +0100
+++ gcc/range-op-float.cc	2022-12-05 16:13:54.414845672 +0100
@@ -330,6 +330,18 @@  frange_drop_ninf (frange &r, tree type)
   r.intersect (tmp);
 }
 
+// Crop R to [MIN, MAX] where MAX is the maximum representable number
+// for TYPE and MIN the minimum representable number for TYPE.
+
+static inline void
+frange_drop_infs (frange &r, tree type)
+{
+  REAL_VALUE_TYPE max = real_max_representable (type);
+  REAL_VALUE_TYPE min = real_min_representable (type);
+  frange tmp (type, min, max);
+  r.intersect (tmp);
+}
+
 // If zero is in R, make sure both -0.0 and +0.0 are in the range.
 
 static inline void
@@ -1883,7 +1895,7 @@  foperator_unordered_equal::op1_range (fr
 
 static bool
 float_binary_op_range_finish (bool ret, frange &r, tree type,
-			      const frange &lhs)
+			      const frange &lhs, enum tree_code op)
 {
   if (!ret)
     return false;
@@ -1904,7 +1916,16 @@  float_binary_op_range_finish (bool ret,
   // If lhs isn't NAN, then neither operand could be NAN,
   // even if the reverse operation does introduce a maybe_nan.
   if (!lhs.maybe_isnan ())
-    r.clear_nan ();
+    {
+      r.clear_nan ();
+      if (op != RDIV_EXPR
+	  && !real_isinf (&lhs.lower_bound ())
+	  && !real_isinf (&lhs.upper_bound ()))
+	// For reverse + or - or *, if result is finite, then r must
+	// be finite too, as X + INF or X - INF or X * INF is always
+	// +-INF or NAN.  For division handle it in the caller.
+	frange_drop_infs (r, type);
+    }
   // If lhs is a maybe or known NAN, the operand could be
   // NAN.
   else
@@ -2020,7 +2041,7 @@  public:
     if (!minus)
       return false;
     return float_binary_op_range_finish (minus.fold_range (r, type, lhs, op2),
-					 r, type, lhs);
+					 r, type, lhs, PLUS_EXPR);
   }
   virtual bool op2_range (frange &r, tree type,
 			  const frange &lhs,
@@ -2067,7 +2088,7 @@  public:
       return false;
     return float_binary_op_range_finish (fop_plus.fold_range (r, type, lhs,
 							      op2),
-					 r, type, lhs);
+					 r, type, lhs, MINUS_EXPR);
   }
   virtual bool op2_range (frange &r, tree type,
 			  const frange &lhs,
@@ -2077,7 +2098,7 @@  public:
     if (lhs.undefined_p ())
       return false;
     return float_binary_op_range_finish (fold_range (r, type, op1, lhs),
-					 r, type, lhs);
+					 r, type, lhs, MINUS_EXPR);
   }
 private:
   void rv_fold (REAL_VALUE_TYPE &lb, REAL_VALUE_TYPE &ub, bool &maybe_nan,
@@ -2166,7 +2187,7 @@  public:
     // or if lhs must be zero and op2 doesn't include zero, it would be
     // UNDEFINED, while rdiv.fold_range computes a zero or singleton INF
     // range.  Those are supersets of UNDEFINED, so let's keep that way.
-    return float_binary_op_range_finish (ret, r, type, lhs);
+    return float_binary_op_range_finish (ret, r, type, lhs, MULT_EXPR);
   }
   virtual bool op2_range (frange &r, tree type,
 			  const frange &lhs,
@@ -2313,7 +2334,12 @@  public:
 	zero_to_inf_range (lb, ub, signbit_known);
 	r.set (type, lb, ub);
       }
-    return float_binary_op_range_finish (ret, r, type, lhs);
+    // If lhs must be finite (can't be +-INF nor NAN), then op1 must be
+    // finite too - +-INF / anything is either +-INF or NAN (NAN if op2 is
+    // +-INF or NAN).
+    if (!real_isinf (&lhs_lb) && !real_isinf (&lhs_ub) && !lhs.maybe_isnan ())
+      frange_drop_infs (r, type);
+    return float_binary_op_range_finish (ret, r, type, lhs, RDIV_EXPR);
   }
   virtual bool op2_range (frange &r, tree type,
 			  const frange &lhs,
@@ -2341,7 +2367,11 @@  public:
 	zero_to_inf_range (lb, ub, signbit_known);
 	r.set (type, lb, ub);
       }
-    return float_binary_op_range_finish (ret, r, type, lhs);
+    // If lhs can't be zero nor NAN, then op2 must be finite - anything / +-INF
+    // is either +-0 or NAN (NAN if op1 is +-INF or NAN).
+    if (!contains_zero_p (lhs_lb, lhs_ub) && !lhs.maybe_isnan ())
+      frange_drop_infs (r, type);
+    return float_binary_op_range_finish (ret, r, type, lhs, RDIV_EXPR);
   }
 private:
   void rv_fold (REAL_VALUE_TYPE &lb, REAL_VALUE_TYPE &ub, bool &maybe_nan,