mirror of
https://forge.sourceware.org/marek/gcc.git
synced 2026-02-22 12:00:11 -05:00
3260 lines
87 KiB
C++
3260 lines
87 KiB
C++
/* Floating point range operators.
|
|
Copyright (C) 2022-2026 Free Software Foundation, Inc.
|
|
Contributed by Aldy Hernandez <aldyh@redhat.com>.
|
|
|
|
This file is part of GCC.
|
|
|
|
GCC is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3, or (at your option)
|
|
any later version.
|
|
|
|
GCC is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GCC; see the file COPYING3. If not see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
#include "backend.h"
|
|
#include "insn-codes.h"
|
|
#include "rtl.h"
|
|
#include "tree.h"
|
|
#include "gimple.h"
|
|
#include "cfghooks.h"
|
|
#include "tree-pass.h"
|
|
#include "ssa.h"
|
|
#include "optabs-tree.h"
|
|
#include "gimple-pretty-print.h"
|
|
#include "diagnostic-core.h"
|
|
#include "flags.h"
|
|
#include "fold-const.h"
|
|
#include "stor-layout.h"
|
|
#include "calls.h"
|
|
#include "cfganal.h"
|
|
#include "gimple-iterator.h"
|
|
#include "gimple-fold.h"
|
|
#include "tree-eh.h"
|
|
#include "gimple-walk.h"
|
|
#include "tree-cfg.h"
|
|
#include "wide-int.h"
|
|
#include "value-relation.h"
|
|
#include "range-op.h"
|
|
#include "range-op-mixed.h"
|
|
|
|
// Default definitions for floating point operators.
|
|
|
|
bool
|
|
range_operator::fold_range (frange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio trio) const
|
|
{
|
|
if (empty_range_varying (r, type, op1, op2))
|
|
return true;
|
|
if (op1.known_isnan () || op2.known_isnan ())
|
|
{
|
|
r.set_nan (type);
|
|
return true;
|
|
}
|
|
|
|
rv_fold (r, type,
|
|
op1.lower_bound (), op1.upper_bound (),
|
|
op2.lower_bound (), op2.upper_bound (), trio.op1_op2 ());
|
|
|
|
if (r.known_isnan ())
|
|
return true;
|
|
if (op1.maybe_isnan () || op2.maybe_isnan ())
|
|
r.update_nan ();
|
|
|
|
// If the result has overflowed and flag_trapping_math, folding this
|
|
// operation could elide an overflow or division by zero exception.
|
|
// Avoid returning a singleton +-INF, to keep the propagators (DOM
|
|
// and substitute_and_fold_engine) from folding. See PR107608.
|
|
if (flag_trapping_math
|
|
&& MODE_HAS_INFINITIES (TYPE_MODE (type))
|
|
&& r.known_isinf () && !op1.known_isinf () && !op2.known_isinf ())
|
|
{
|
|
REAL_VALUE_TYPE inf = r.lower_bound ();
|
|
if (real_isneg (&inf))
|
|
{
|
|
REAL_VALUE_TYPE min = real_min_representable (type);
|
|
r.set (type, inf, min);
|
|
}
|
|
else
|
|
{
|
|
REAL_VALUE_TYPE max = real_max_representable (type);
|
|
r.set (type, max, inf);
|
|
}
|
|
}
|
|
|
|
r.flush_denormals_to_zero ();
|
|
|
|
return true;
|
|
}
|
|
|
|
// For a given operation, fold two sets of ranges into [lb, ub].
|
|
// MAYBE_NAN is set to TRUE if, in addition to any result in LB or
|
|
// UB, the final range has the possibility of a NAN.
|
|
void
|
|
range_operator::rv_fold (frange &r, tree type,
|
|
const REAL_VALUE_TYPE &,
|
|
const REAL_VALUE_TYPE &,
|
|
const REAL_VALUE_TYPE &,
|
|
const REAL_VALUE_TYPE &, relation_kind) const
|
|
{
|
|
r.set (type, dconstninf, dconstinf, nan_state (true));
|
|
}
|
|
|
|
bool
|
|
range_operator::fold_range (irange &r ATTRIBUTE_UNUSED,
|
|
tree type ATTRIBUTE_UNUSED,
|
|
const frange &lh ATTRIBUTE_UNUSED,
|
|
const irange &rh ATTRIBUTE_UNUSED,
|
|
relation_trio) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
range_operator::fold_range (irange &r ATTRIBUTE_UNUSED,
|
|
tree type ATTRIBUTE_UNUSED,
|
|
const frange &lh ATTRIBUTE_UNUSED,
|
|
const frange &rh ATTRIBUTE_UNUSED,
|
|
relation_trio) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
range_operator::fold_range (frange &r ATTRIBUTE_UNUSED,
|
|
tree type ATTRIBUTE_UNUSED,
|
|
const irange &lh ATTRIBUTE_UNUSED,
|
|
const irange &rh ATTRIBUTE_UNUSED,
|
|
relation_trio) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
range_operator::op1_range (frange &r ATTRIBUTE_UNUSED,
|
|
tree type ATTRIBUTE_UNUSED,
|
|
const frange &lhs ATTRIBUTE_UNUSED,
|
|
const frange &op2 ATTRIBUTE_UNUSED,
|
|
relation_trio) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
range_operator::op1_range (frange &r ATTRIBUTE_UNUSED,
|
|
tree type ATTRIBUTE_UNUSED,
|
|
const irange &lhs ATTRIBUTE_UNUSED,
|
|
const frange &op2 ATTRIBUTE_UNUSED,
|
|
relation_trio) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
range_operator::op2_range (frange &r ATTRIBUTE_UNUSED,
|
|
tree type ATTRIBUTE_UNUSED,
|
|
const frange &lhs ATTRIBUTE_UNUSED,
|
|
const frange &op1 ATTRIBUTE_UNUSED,
|
|
relation_trio) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
range_operator::op2_range (frange &r ATTRIBUTE_UNUSED,
|
|
tree type ATTRIBUTE_UNUSED,
|
|
const irange &lhs ATTRIBUTE_UNUSED,
|
|
const frange &op1 ATTRIBUTE_UNUSED,
|
|
relation_trio) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
relation_kind
|
|
range_operator::lhs_op1_relation (const frange &lhs ATTRIBUTE_UNUSED,
|
|
const frange &op1 ATTRIBUTE_UNUSED,
|
|
const frange &op2 ATTRIBUTE_UNUSED,
|
|
relation_kind) const
|
|
{
|
|
return VREL_VARYING;
|
|
}
|
|
|
|
relation_kind
|
|
range_operator::lhs_op1_relation (const irange &lhs ATTRIBUTE_UNUSED,
|
|
const frange &op1 ATTRIBUTE_UNUSED,
|
|
const frange &op2 ATTRIBUTE_UNUSED,
|
|
relation_kind) const
|
|
{
|
|
return VREL_VARYING;
|
|
}
|
|
|
|
relation_kind
|
|
range_operator::lhs_op2_relation (const irange &lhs ATTRIBUTE_UNUSED,
|
|
const frange &op1 ATTRIBUTE_UNUSED,
|
|
const frange &op2 ATTRIBUTE_UNUSED,
|
|
relation_kind) const
|
|
{
|
|
return VREL_VARYING;
|
|
}
|
|
|
|
relation_kind
|
|
range_operator::lhs_op2_relation (const frange &lhs ATTRIBUTE_UNUSED,
|
|
const frange &op1 ATTRIBUTE_UNUSED,
|
|
const frange &op2 ATTRIBUTE_UNUSED,
|
|
relation_kind) const
|
|
{
|
|
return VREL_VARYING;
|
|
}
|
|
|
|
relation_kind
|
|
range_operator::op1_op2_relation (const irange &,
|
|
const frange &,
|
|
const frange &) const
|
|
{
|
|
return VREL_VARYING;
|
|
}
|
|
|
|
|
|
relation_kind
|
|
range_operator::op1_op2_relation (const frange &,
|
|
const frange &,
|
|
const frange &) const
|
|
{
|
|
return VREL_VARYING;
|
|
}
|
|
|
|
// Return TRUE if OP1 and OP2 may be a NAN.
|
|
|
|
static inline bool
|
|
maybe_isnan (const frange &op1, const frange &op2)
|
|
{
|
|
return op1.maybe_isnan () || op2.maybe_isnan ();
|
|
}
|
|
|
|
// Floating point version of relop_early_resolve that takes NANs into
|
|
// account.
|
|
//
|
|
// For relation opcodes, first try to see if the supplied relation
|
|
// forces a true or false result, and return that.
|
|
// Then check for undefined operands. If none of this applies,
|
|
// return false.
|
|
//
|
|
// TRIO are the relations between operands as they appear in the IL.
|
|
// MY_REL is the relation that corresponds to the operator being
|
|
// folded. For example, when attempting to fold x_3 == y_5, MY_REL is
|
|
// VREL_EQ, and if the statement is dominated by x_3 > y_5, then
|
|
// TRIO.op1_op2() is VREL_GT.
|
|
|
|
static inline bool
|
|
frelop_early_resolve (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio trio, relation_kind my_rel)
|
|
{
|
|
relation_kind rel = trio.op1_op2 ();
|
|
|
|
// If known relation is a complete subset of this relation, always
|
|
// return true. However, avoid doing this when NAN is a possibility
|
|
// as we'll incorrectly fold conditions:
|
|
//
|
|
// if (x_3 >= y_5)
|
|
// ;
|
|
// else
|
|
// ;; With NANs the relation here is basically VREL_UNLT, so we
|
|
// ;; can't fold the following:
|
|
// if (x_3 < y_5)
|
|
if (!maybe_isnan (op1, op2) && relation_union (rel, my_rel) == my_rel)
|
|
{
|
|
r = range_true (type);
|
|
return true;
|
|
}
|
|
|
|
// If known relation has no subset of this relation, always false.
|
|
if (relation_intersect (rel, my_rel) == VREL_UNDEFINED)
|
|
{
|
|
r = range_false (type);
|
|
return true;
|
|
}
|
|
|
|
// If either operand is undefined, return VARYING.
|
|
if (empty_range_varying (r, type, op1, op2))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Set VALUE to its next real value, or INF if the operation overflows.
|
|
|
|
void
|
|
frange_nextafter (enum machine_mode mode,
|
|
REAL_VALUE_TYPE &value,
|
|
const REAL_VALUE_TYPE &inf)
|
|
{
|
|
if (MODE_COMPOSITE_P (mode)
|
|
&& (real_isdenormal (&value, mode) || real_iszero (&value)))
|
|
{
|
|
// IBM extended denormals only have DFmode precision.
|
|
REAL_VALUE_TYPE tmp, tmp2;
|
|
real_convert (&tmp2, DFmode, &value);
|
|
real_nextafter (&tmp, REAL_MODE_FORMAT (DFmode), &tmp2, &inf);
|
|
real_convert (&value, mode, &tmp);
|
|
}
|
|
else
|
|
{
|
|
REAL_VALUE_TYPE tmp;
|
|
real_nextafter (&tmp, REAL_MODE_FORMAT (mode), &value, &inf);
|
|
value = tmp;
|
|
}
|
|
}
|
|
|
|
// Like real_arithmetic, but round the result to INF if the operation
|
|
// produced inexact results.
|
|
//
|
|
// ?? There is still one problematic case, i387. With
|
|
// -fexcess-precision=standard we perform most SF/DFmode arithmetic in
|
|
// XFmode (long_double_type_node), so that case is OK. But without
|
|
// -mfpmath=sse, all the SF/DFmode computations are in XFmode
|
|
// precision (64-bit mantissa) and only occasionally rounded to
|
|
// SF/DFmode (when storing into memory from the 387 stack). Maybe
|
|
// this is ok as well though it is just occasionally more precise. ??
|
|
|
|
void
|
|
frange_arithmetic (enum tree_code code, tree type,
|
|
REAL_VALUE_TYPE &result,
|
|
const REAL_VALUE_TYPE &op1,
|
|
const REAL_VALUE_TYPE &op2,
|
|
const REAL_VALUE_TYPE &inf)
|
|
{
|
|
REAL_VALUE_TYPE value;
|
|
enum machine_mode mode = TYPE_MODE (type);
|
|
bool mode_composite = MODE_COMPOSITE_P (mode);
|
|
|
|
bool inexact = real_arithmetic (&value, code, &op1, &op2);
|
|
real_convert (&result, mode, &value);
|
|
|
|
/* When rounding towards negative infinity, x + (-x) and
|
|
x - x is -0 rather than +0 real_arithmetic computes.
|
|
So, when we are looking for lower bound (inf is negative),
|
|
use -0 rather than +0. */
|
|
if (flag_rounding_math
|
|
&& (code == PLUS_EXPR || code == MINUS_EXPR)
|
|
&& !inexact
|
|
&& real_iszero (&result)
|
|
&& !real_isneg (&result)
|
|
&& real_isneg (&inf))
|
|
{
|
|
REAL_VALUE_TYPE op2a = op2;
|
|
if (code == PLUS_EXPR)
|
|
op2a.sign ^= 1;
|
|
if (real_isneg (&op1) == real_isneg (&op2a) && real_equal (&op1, &op2a))
|
|
result.sign = 1;
|
|
}
|
|
|
|
// Be extra careful if there may be discrepancies between the
|
|
// compile and runtime results.
|
|
bool round = false;
|
|
if (mode_composite)
|
|
round = true;
|
|
else
|
|
{
|
|
bool low = real_isneg (&inf);
|
|
round = (low ? !real_less (&result, &value)
|
|
: !real_less (&value, &result));
|
|
if (real_isinf (&result, !low)
|
|
&& !real_isinf (&value)
|
|
&& !flag_rounding_math)
|
|
{
|
|
// Use just [+INF, +INF] rather than [MAX, +INF]
|
|
// even if value is larger than MAX and rounds to
|
|
// nearest to +INF. Similarly just [-INF, -INF]
|
|
// rather than [-INF, +MAX] even if value is smaller
|
|
// than -MAX and rounds to nearest to -INF.
|
|
// Unless INEXACT is true, in that case we need some
|
|
// extra buffer.
|
|
if (!inexact)
|
|
round = false;
|
|
else
|
|
{
|
|
REAL_VALUE_TYPE tmp = result, tmp2;
|
|
frange_nextafter (mode, tmp, inf);
|
|
// TMP is at this point the maximum representable
|
|
// number.
|
|
real_arithmetic (&tmp2, MINUS_EXPR, &value, &tmp);
|
|
if (real_isneg (&tmp2) != low
|
|
&& (REAL_EXP (&tmp2) - REAL_EXP (&tmp)
|
|
>= 2 - REAL_MODE_FORMAT (mode)->p))
|
|
round = false;
|
|
}
|
|
}
|
|
}
|
|
if (round && (inexact || !real_identical (&result, &value)))
|
|
{
|
|
if (mode_composite
|
|
&& (real_isdenormal (&result, mode) || real_iszero (&result)))
|
|
{
|
|
// IBM extended denormals only have DFmode precision.
|
|
REAL_VALUE_TYPE tmp, tmp2;
|
|
real_convert (&tmp2, DFmode, &value);
|
|
real_nextafter (&tmp, REAL_MODE_FORMAT (DFmode), &tmp2, &inf);
|
|
real_convert (&result, mode, &tmp);
|
|
}
|
|
else
|
|
frange_nextafter (mode, result, inf);
|
|
}
|
|
if (mode_composite)
|
|
switch (code)
|
|
{
|
|
case PLUS_EXPR:
|
|
case MINUS_EXPR:
|
|
// ibm-ldouble-format documents 1ulp for + and -.
|
|
frange_nextafter (mode, result, inf);
|
|
break;
|
|
case MULT_EXPR:
|
|
// ibm-ldouble-format documents 2ulps for *.
|
|
frange_nextafter (mode, result, inf);
|
|
frange_nextafter (mode, result, inf);
|
|
break;
|
|
case RDIV_EXPR:
|
|
// ibm-ldouble-format documents 3ulps for /.
|
|
frange_nextafter (mode, result, inf);
|
|
frange_nextafter (mode, result, inf);
|
|
frange_nextafter (mode, result, inf);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Crop R to [-INF, MAX] where MAX is the maximum representable number
|
|
// for TYPE.
|
|
|
|
static inline void
|
|
frange_drop_inf (frange &r, tree type)
|
|
{
|
|
REAL_VALUE_TYPE max = real_max_representable (type);
|
|
frange tmp (type, r.lower_bound (), max);
|
|
r.intersect (tmp);
|
|
}
|
|
|
|
// Crop R to [MIN, +INF] where MIN is the minimum representable number
|
|
// for TYPE.
|
|
|
|
static inline void
|
|
frange_drop_ninf (frange &r, tree type)
|
|
{
|
|
REAL_VALUE_TYPE min = real_min_representable (type);
|
|
frange tmp (type, min, r.upper_bound ());
|
|
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
|
|
frange_add_zeros (frange &r, tree type)
|
|
{
|
|
if (r.undefined_p () || r.known_isnan ())
|
|
return;
|
|
|
|
if (HONOR_SIGNED_ZEROS (type)
|
|
&& (real_iszero (&r.lower_bound ()) || real_iszero (&r.upper_bound ())))
|
|
{
|
|
frange zero;
|
|
zero.set_zero (type);
|
|
r.union_ (zero);
|
|
}
|
|
}
|
|
|
|
// Build a range that is <= VAL and store it in R. Return TRUE if
|
|
// further changes may be needed for R, or FALSE if R is in its final
|
|
// form.
|
|
|
|
static bool
|
|
build_le (frange &r, tree type, const frange &val)
|
|
{
|
|
gcc_checking_assert (!val.known_isnan ());
|
|
|
|
REAL_VALUE_TYPE ninf = frange_val_min (type);
|
|
r.set (type, ninf, val.upper_bound ());
|
|
|
|
// Add both zeros if there's the possibility of zero equality.
|
|
frange_add_zeros (r, type);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Build a range that is < VAL and store it in R. Return TRUE if
|
|
// further changes may be needed for R, or FALSE if R is in its final
|
|
// form.
|
|
|
|
static bool
|
|
build_lt (frange &r, tree type, const frange &val)
|
|
{
|
|
gcc_checking_assert (!val.known_isnan ());
|
|
|
|
// < -INF is outside the range.
|
|
if (real_isinf (&val.upper_bound (), 1))
|
|
{
|
|
if (HONOR_NANS (type))
|
|
r.set_nan (type);
|
|
else
|
|
r.set_undefined ();
|
|
return false;
|
|
}
|
|
|
|
REAL_VALUE_TYPE ninf = frange_val_min (type);
|
|
REAL_VALUE_TYPE prev = val.upper_bound ();
|
|
machine_mode mode = TYPE_MODE (type);
|
|
// Default to the conservatively correct closed ranges for
|
|
// MODE_COMPOSITE_P, otherwise use nextafter. Note that for
|
|
// !HONOR_INFINITIES, nextafter will yield -INF, but frange::set()
|
|
// will crop the range appropriately.
|
|
if (!MODE_COMPOSITE_P (mode))
|
|
frange_nextafter (mode, prev, ninf);
|
|
r.set (type, ninf, prev);
|
|
return true;
|
|
}
|
|
|
|
// Build a range that is >= VAL and store it in R. Return TRUE if
|
|
// further changes may be needed for R, or FALSE if R is in its final
|
|
// form.
|
|
|
|
static bool
|
|
build_ge (frange &r, tree type, const frange &val)
|
|
{
|
|
gcc_checking_assert (!val.known_isnan ());
|
|
|
|
REAL_VALUE_TYPE inf = frange_val_max (type);
|
|
r.set (type, val.lower_bound (), inf);
|
|
|
|
// Add both zeros if there's the possibility of zero equality.
|
|
frange_add_zeros (r, type);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Build a range that is > VAL and store it in R. Return TRUE if
|
|
// further changes may be needed for R, or FALSE if R is in its final
|
|
// form.
|
|
|
|
static bool
|
|
build_gt (frange &r, tree type, const frange &val)
|
|
{
|
|
gcc_checking_assert (!val.known_isnan ());
|
|
|
|
// > +INF is outside the range.
|
|
if (real_isinf (&val.lower_bound (), 0))
|
|
{
|
|
if (HONOR_NANS (type))
|
|
r.set_nan (type);
|
|
else
|
|
r.set_undefined ();
|
|
return false;
|
|
}
|
|
|
|
REAL_VALUE_TYPE inf = frange_val_max (type);
|
|
REAL_VALUE_TYPE next = val.lower_bound ();
|
|
machine_mode mode = TYPE_MODE (type);
|
|
// Default to the conservatively correct closed ranges for
|
|
// MODE_COMPOSITE_P, otherwise use nextafter. Note that for
|
|
// !HONOR_INFINITIES, nextafter will yield +INF, but frange::set()
|
|
// will crop the range appropriately.
|
|
if (!MODE_COMPOSITE_P (mode))
|
|
frange_nextafter (mode, next, inf);
|
|
r.set (type, next, inf);
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
operator_identity::fold_range (frange &r, tree, const frange &op1,
|
|
const frange &, relation_trio) const
|
|
{
|
|
r = op1;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_identity::op1_range (frange &r, tree, const frange &lhs,
|
|
const frange &, relation_trio) const
|
|
{
|
|
r = lhs;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_cst::fold_range (frange &r, tree, const frange &op1,
|
|
const frange &, relation_trio) const
|
|
{
|
|
r = op1;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_equal::op2_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op1,
|
|
relation_trio rel) const
|
|
{
|
|
return op1_range (r, type, lhs, op1, rel.swap_op1_op2 ());
|
|
}
|
|
|
|
bool
|
|
operator_equal::fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio rel) const
|
|
{
|
|
if (frelop_early_resolve (r, type, op1, op2, rel, VREL_EQ))
|
|
return true;
|
|
|
|
if (op1.known_isnan () || op2.known_isnan ())
|
|
r = range_false (type);
|
|
// We can be sure the values are always equal or not if both ranges
|
|
// consist of a single value, and then compare them.
|
|
else if (op1.singleton_p () && op2.singleton_p ())
|
|
{
|
|
if (op1 == op2)
|
|
r = range_true (type);
|
|
// If one operand is -0.0 and other 0.0, they are still equal.
|
|
else if (real_iszero (&op1.lower_bound ())
|
|
&& real_iszero (&op2.lower_bound ()))
|
|
r = range_true (type);
|
|
else
|
|
r = range_false (type);
|
|
}
|
|
else if (real_iszero (&op1.lower_bound ())
|
|
&& real_iszero (&op1.upper_bound ())
|
|
&& real_iszero (&op2.lower_bound ())
|
|
&& real_iszero (&op2.upper_bound ())
|
|
&& !maybe_isnan (op1, op2))
|
|
// [-0.0, 0.0] == [-0.0, 0.0] or similar.
|
|
r = range_true (type);
|
|
else
|
|
{
|
|
// If ranges do not intersect, we know the range is not equal,
|
|
// otherwise we don't know anything for sure.
|
|
frange tmp = op1;
|
|
tmp.intersect (op2);
|
|
if (tmp.undefined_p ())
|
|
{
|
|
// If one range is [whatever, -0.0] and another
|
|
// [0.0, whatever2], we don't know anything either,
|
|
// because -0.0 == 0.0.
|
|
if ((real_iszero (&op1.upper_bound ())
|
|
&& real_iszero (&op2.lower_bound ()))
|
|
|| (real_iszero (&op1.lower_bound ())
|
|
&& real_iszero (&op2.upper_bound ())))
|
|
r = range_true_and_false (type);
|
|
else
|
|
r = range_false (type);
|
|
}
|
|
else
|
|
r = range_true_and_false (type);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_equal::op1_range (frange &r, tree type,
|
|
const irange &lhs,
|
|
const frange &op2,
|
|
relation_trio trio) const
|
|
{
|
|
relation_kind rel = trio.op1_op2 ();
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
// The TRUE side of x == NAN is unreachable.
|
|
if (op2.known_isnan ())
|
|
r.set_undefined ();
|
|
else
|
|
{
|
|
// If it's true, the result is the same as OP2.
|
|
r = op2;
|
|
// Add both zeros if there's the possibility of zero equality.
|
|
frange_add_zeros (r, type);
|
|
// The TRUE side of op1 == op2 implies op1 is !NAN.
|
|
r.clear_nan ();
|
|
}
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// The FALSE side of op1 == op1 implies op1 is a NAN.
|
|
if (rel == VREL_EQ)
|
|
r.set_nan (type);
|
|
// On the FALSE side of x == NAN, we know nothing about x.
|
|
else if (op2.known_isnan ())
|
|
r.set_varying (type);
|
|
// If the result is false, the only time we know anything is
|
|
// if OP2 is a constant.
|
|
else if (op2.singleton_p ()
|
|
|| (!op2.maybe_isnan () && op2.zero_p ()))
|
|
{
|
|
REAL_VALUE_TYPE tmp = op2.lower_bound ();
|
|
r.set (type, tmp, tmp, VR_ANTI_RANGE);
|
|
}
|
|
else
|
|
r.set_varying (type);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Check if the LHS range indicates a relation between OP1 and OP2.
|
|
|
|
relation_kind
|
|
operator_equal::op1_op2_relation (const irange &lhs, const frange &,
|
|
const frange &) const
|
|
{
|
|
if (lhs.undefined_p ())
|
|
return VREL_UNDEFINED;
|
|
|
|
// FALSE = op1 == op2 indicates NE_EXPR.
|
|
if (lhs.zero_p ())
|
|
return VREL_NE;
|
|
|
|
// TRUE = op1 == op2 indicates EQ_EXPR.
|
|
if (!contains_zero_p (lhs))
|
|
return VREL_EQ;
|
|
return VREL_VARYING;
|
|
}
|
|
|
|
bool
|
|
operator_not_equal::fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio trio) const
|
|
{
|
|
relation_kind rel = trio.op1_op2 ();
|
|
|
|
// VREL_NE & NE_EXPR is always true, even with NANs.
|
|
if (rel == VREL_NE)
|
|
{
|
|
r = range_true (type);
|
|
return true;
|
|
}
|
|
if (rel == VREL_EQ && maybe_isnan (op1, op2))
|
|
{
|
|
// Avoid frelop_early_resolve() below as it could fold to FALSE
|
|
// without regards to NANs. This would be incorrect if trying
|
|
// to fold x_5 != x_5 without prior knowledge of NANs.
|
|
}
|
|
else if (frelop_early_resolve (r, type, op1, op2, trio, VREL_NE))
|
|
return true;
|
|
|
|
// x != NAN is always TRUE.
|
|
if (op1.known_isnan () || op2.known_isnan ())
|
|
r = range_true (type);
|
|
// We can be sure the values are always equal or not if both ranges
|
|
// consist of a single value, and then compare them.
|
|
else if (op1.singleton_p () && op2.singleton_p ())
|
|
{
|
|
if (op1 == op2)
|
|
r = range_false (type);
|
|
// If one operand is -0.0 and other 0.0, they are still equal.
|
|
else if (real_iszero (&op1.lower_bound ())
|
|
&& real_iszero (&op2.lower_bound ()))
|
|
r = range_false (type);
|
|
else
|
|
r = range_true (type);
|
|
}
|
|
else if (real_iszero (&op1.lower_bound ())
|
|
&& real_iszero (&op1.upper_bound ())
|
|
&& real_iszero (&op2.lower_bound ())
|
|
&& real_iszero (&op2.upper_bound ())
|
|
&& !maybe_isnan (op1, op2))
|
|
// [-0.0, 0.0] != [-0.0, 0.0] or similar.
|
|
r = range_false (type);
|
|
else
|
|
{
|
|
// If ranges do not intersect, we know the range is not equal,
|
|
// otherwise we don't know anything for sure.
|
|
frange tmp = op1;
|
|
tmp.intersect (op2);
|
|
if (tmp.undefined_p ())
|
|
{
|
|
// If one range is [whatever, -0.0] and another
|
|
// [0.0, whatever2], we don't know anything either,
|
|
// because -0.0 == 0.0.
|
|
if ((real_iszero (&op1.upper_bound ())
|
|
&& real_iszero (&op2.lower_bound ()))
|
|
|| (real_iszero (&op1.lower_bound ())
|
|
&& real_iszero (&op2.upper_bound ())))
|
|
r = range_true_and_false (type);
|
|
else
|
|
r = range_true (type);
|
|
}
|
|
else
|
|
r = range_true_and_false (type);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_not_equal::op1_range (frange &r, tree type,
|
|
const irange &lhs,
|
|
const frange &op2,
|
|
relation_trio trio) const
|
|
{
|
|
relation_kind rel = trio.op1_op2 ();
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
// If the result is true, the only time we know anything is if
|
|
// OP2 is a constant.
|
|
if (op2.singleton_p ())
|
|
{
|
|
// This is correct even if op1 is NAN, because the following
|
|
// range would be ~[tmp, tmp] with the NAN property set to
|
|
// maybe (VARYING).
|
|
REAL_VALUE_TYPE tmp = op2.lower_bound ();
|
|
r.set (type, tmp, tmp, VR_ANTI_RANGE);
|
|
}
|
|
// The TRUE side of op1 != op1 implies op1 is NAN.
|
|
else if (rel == VREL_EQ)
|
|
r.set_nan (type);
|
|
else
|
|
r.set_varying (type);
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// The FALSE side of x != NAN is impossible.
|
|
if (op2.known_isnan ())
|
|
r.set_undefined ();
|
|
else
|
|
{
|
|
// If it's false, the result is the same as OP2.
|
|
r = op2;
|
|
// Add both zeros if there's the possibility of zero equality.
|
|
frange_add_zeros (r, type);
|
|
// The FALSE side of op1 != op2 implies op1 is !NAN.
|
|
r.clear_nan ();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_not_equal::op2_range (frange &r, tree type,
|
|
const irange &lhs,
|
|
const frange &op1,
|
|
relation_trio trio) const
|
|
{
|
|
return op1_range (r, type, lhs, op1, trio);
|
|
}
|
|
|
|
// Check if the LHS range indicates a relation between OP1 and OP2.
|
|
|
|
relation_kind
|
|
operator_not_equal::op1_op2_relation (const irange &lhs, const frange &,
|
|
const frange &) const
|
|
{
|
|
if (lhs.undefined_p ())
|
|
return VREL_UNDEFINED;
|
|
|
|
// FALSE = op1 != op2 indicates EQ_EXPR.
|
|
if (lhs.zero_p ())
|
|
return VREL_EQ;
|
|
|
|
// TRUE = op1 != op2 indicates NE_EXPR.
|
|
if (!contains_zero_p (lhs))
|
|
return VREL_NE;
|
|
return VREL_VARYING;
|
|
}
|
|
|
|
bool
|
|
operator_lt::fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio trio) const
|
|
{
|
|
if (frelop_early_resolve (r, type, op1, op2, trio, VREL_LT))
|
|
return true;
|
|
|
|
if (op1.known_isnan ()
|
|
|| op2.known_isnan ()
|
|
|| !real_less (&op1.lower_bound (), &op2.upper_bound ()))
|
|
r = range_false (type);
|
|
else if (!maybe_isnan (op1, op2)
|
|
&& real_less (&op1.upper_bound (), &op2.lower_bound ()))
|
|
r = range_true (type);
|
|
else
|
|
r = range_true_and_false (type);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_lt::op1_range (frange &r,
|
|
tree type,
|
|
const irange &lhs,
|
|
const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
// The TRUE side of x < NAN is unreachable.
|
|
if (op2.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (op2.undefined_p ())
|
|
return false;
|
|
else if (build_lt (r, type, op2))
|
|
{
|
|
r.clear_nan ();
|
|
// x < y implies x is not +INF.
|
|
frange_drop_inf (r, type);
|
|
}
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// On the FALSE side of x < NAN, we know nothing about x.
|
|
if (op2.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else
|
|
build_ge (r, type, op2);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_lt::op2_range (frange &r,
|
|
tree type,
|
|
const irange &lhs,
|
|
const frange &op1,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
// The TRUE side of NAN < x is unreachable.
|
|
if (op1.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (op1.undefined_p ())
|
|
return false;
|
|
else if (build_gt (r, type, op1))
|
|
{
|
|
r.clear_nan ();
|
|
// x < y implies y is not -INF.
|
|
frange_drop_ninf (r, type);
|
|
}
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// On the FALSE side of NAN < x, we know nothing about x.
|
|
if (op1.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else
|
|
build_le (r, type, op1);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// Check if the LHS range indicates a relation between OP1 and OP2.
|
|
|
|
relation_kind
|
|
operator_lt::op1_op2_relation (const irange &lhs, const frange &,
|
|
const frange &) const
|
|
{
|
|
if (lhs.undefined_p ())
|
|
return VREL_UNDEFINED;
|
|
|
|
// FALSE = op1 < op2 indicates GE_EXPR.
|
|
if (lhs.zero_p ())
|
|
return VREL_GE;
|
|
|
|
// TRUE = op1 < op2 indicates LT_EXPR.
|
|
if (!contains_zero_p (lhs))
|
|
return VREL_LT;
|
|
return VREL_VARYING;
|
|
}
|
|
|
|
bool
|
|
operator_le::fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio rel) const
|
|
{
|
|
if (frelop_early_resolve (r, type, op1, op2, rel, VREL_LE))
|
|
return true;
|
|
|
|
if (op1.known_isnan ()
|
|
|| op2.known_isnan ()
|
|
|| !real_compare (LE_EXPR, &op1.lower_bound (), &op2.upper_bound ()))
|
|
r = range_false (type);
|
|
else if (!maybe_isnan (op1, op2)
|
|
&& real_compare (LE_EXPR, &op1.upper_bound (), &op2.lower_bound ()))
|
|
r = range_true (type);
|
|
else
|
|
r = range_true_and_false (type);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_le::op1_range (frange &r,
|
|
tree type,
|
|
const irange &lhs,
|
|
const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
// The TRUE side of x <= NAN is unreachable.
|
|
if (op2.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (op2.undefined_p ())
|
|
return false;
|
|
else if (build_le (r, type, op2))
|
|
r.clear_nan ();
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// On the FALSE side of x <= NAN, we know nothing about x.
|
|
if (op2.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else
|
|
build_gt (r, type, op2);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_le::op2_range (frange &r,
|
|
tree type,
|
|
const irange &lhs,
|
|
const frange &op1,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
// The TRUE side of NAN <= x is unreachable.
|
|
if (op1.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (op1.undefined_p ())
|
|
return false;
|
|
else if (build_ge (r, type, op1))
|
|
r.clear_nan ();
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// On the FALSE side of NAN <= x, we know nothing about x.
|
|
if (op1.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else if (op1.undefined_p ())
|
|
return false;
|
|
else
|
|
build_lt (r, type, op1);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Check if the LHS range indicates a relation between OP1 and OP2.
|
|
|
|
relation_kind
|
|
operator_le::op1_op2_relation (const irange &lhs, const frange &,
|
|
const frange &) const
|
|
{
|
|
if (lhs.undefined_p ())
|
|
return VREL_UNDEFINED;
|
|
|
|
// FALSE = op1 <= op2 indicates GT_EXPR.
|
|
if (lhs.zero_p ())
|
|
return VREL_GT;
|
|
|
|
// TRUE = op1 <= op2 indicates LE_EXPR.
|
|
if (!contains_zero_p (lhs))
|
|
return VREL_LE;
|
|
return VREL_VARYING;
|
|
}
|
|
|
|
bool
|
|
operator_gt::fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio trio) const
|
|
{
|
|
if (frelop_early_resolve (r, type, op1, op2, trio, VREL_GT))
|
|
return true;
|
|
|
|
if (op1.known_isnan ()
|
|
|| op2.known_isnan ()
|
|
|| !real_compare (GT_EXPR, &op1.upper_bound (), &op2.lower_bound ()))
|
|
r = range_false (type);
|
|
else if (!maybe_isnan (op1, op2)
|
|
&& real_compare (GT_EXPR, &op1.lower_bound (), &op2.upper_bound ()))
|
|
r = range_true (type);
|
|
else
|
|
r = range_true_and_false (type);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_gt::op1_range (frange &r,
|
|
tree type,
|
|
const irange &lhs,
|
|
const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
// The TRUE side of x > NAN is unreachable.
|
|
if (op2.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (op2.undefined_p ())
|
|
return false;
|
|
else if (build_gt (r, type, op2))
|
|
{
|
|
r.clear_nan ();
|
|
// x > y implies x is not -INF.
|
|
frange_drop_ninf (r, type);
|
|
}
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// On the FALSE side of x > NAN, we know nothing about x.
|
|
if (op2.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else if (op2.undefined_p ())
|
|
return false;
|
|
else
|
|
build_le (r, type, op2);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_gt::op2_range (frange &r,
|
|
tree type,
|
|
const irange &lhs,
|
|
const frange &op1,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
// The TRUE side of NAN > x is unreachable.
|
|
if (op1.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (op1.undefined_p ())
|
|
return false;
|
|
else if (build_lt (r, type, op1))
|
|
{
|
|
r.clear_nan ();
|
|
// x > y implies y is not +INF.
|
|
frange_drop_inf (r, type);
|
|
}
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// On The FALSE side of NAN > x, we know nothing about x.
|
|
if (op1.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else if (op1.undefined_p ())
|
|
return false;
|
|
else
|
|
build_ge (r, type, op1);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Check if the LHS range indicates a relation between OP1 and OP2.
|
|
|
|
relation_kind
|
|
operator_gt::op1_op2_relation (const irange &lhs, const frange &,
|
|
const frange &) const
|
|
{
|
|
if (lhs.undefined_p ())
|
|
return VREL_UNDEFINED;
|
|
|
|
// FALSE = op1 > op2 indicates LE_EXPR.
|
|
if (lhs.zero_p ())
|
|
return VREL_LE;
|
|
|
|
// TRUE = op1 > op2 indicates GT_EXPR.
|
|
if (!contains_zero_p (lhs))
|
|
return VREL_GT;
|
|
return VREL_VARYING;
|
|
}
|
|
|
|
bool
|
|
operator_ge::fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio rel) const
|
|
{
|
|
if (frelop_early_resolve (r, type, op1, op2, rel, VREL_GE))
|
|
return true;
|
|
|
|
if (op1.known_isnan ()
|
|
|| op2.known_isnan ()
|
|
|| !real_compare (GE_EXPR, &op1.upper_bound (), &op2.lower_bound ()))
|
|
r = range_false (type);
|
|
else if (!maybe_isnan (op1, op2)
|
|
&& real_compare (GE_EXPR, &op1.lower_bound (), &op2.upper_bound ()))
|
|
r = range_true (type);
|
|
else
|
|
r = range_true_and_false (type);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_ge::op1_range (frange &r,
|
|
tree type,
|
|
const irange &lhs,
|
|
const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
// The TRUE side of x >= NAN is unreachable.
|
|
if (op2.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (op2.undefined_p ())
|
|
return false;
|
|
else if (build_ge (r, type, op2))
|
|
r.clear_nan ();
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// On the FALSE side of x >= NAN, we know nothing about x.
|
|
if (op2.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else if (op2.undefined_p ())
|
|
return false;
|
|
else
|
|
build_lt (r, type, op2);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_ge::op2_range (frange &r, tree type,
|
|
const irange &lhs,
|
|
const frange &op1,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
// The TRUE side of NAN >= x is unreachable.
|
|
if (op1.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (op1.undefined_p ())
|
|
return false;
|
|
else if (build_le (r, type, op1))
|
|
r.clear_nan ();
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// On the FALSE side of NAN >= x, we know nothing about x.
|
|
if (op1.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else if (op1.undefined_p ())
|
|
return false;
|
|
else
|
|
build_gt (r, type, op1);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Check if the LHS range indicates a relation between OP1 and OP2.
|
|
|
|
relation_kind
|
|
operator_ge::op1_op2_relation (const irange &lhs, const frange &,
|
|
const frange &) const
|
|
{
|
|
if (lhs.undefined_p ())
|
|
return VREL_UNDEFINED;
|
|
|
|
// FALSE = op1 >= op2 indicates LT_EXPR.
|
|
if (lhs.zero_p ())
|
|
return VREL_LT;
|
|
|
|
// TRUE = op1 >= op2 indicates GE_EXPR.
|
|
if (!contains_zero_p (lhs))
|
|
return VREL_GE;
|
|
return VREL_VARYING;
|
|
}
|
|
|
|
// UNORDERED_EXPR comparison.
|
|
|
|
class foperator_unordered : public range_operator
|
|
{
|
|
using range_operator::fold_range;
|
|
using range_operator::op1_range;
|
|
using range_operator::op2_range;
|
|
public:
|
|
bool fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio = TRIO_VARYING) const final override;
|
|
bool op1_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op2,
|
|
relation_trio = TRIO_VARYING) const final override;
|
|
bool op2_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op1,
|
|
relation_trio rel = TRIO_VARYING) const final override
|
|
{
|
|
return op1_range (r, type, lhs, op1, rel.swap_op1_op2 ());
|
|
}
|
|
} fop_unordered;
|
|
|
|
bool
|
|
foperator_unordered::fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
// UNORDERED is TRUE if either operand is a NAN.
|
|
if (op1.known_isnan () || op2.known_isnan ())
|
|
r = range_true (type);
|
|
// UNORDERED is FALSE if neither operand is a NAN.
|
|
else if (!op1.maybe_isnan () && !op2.maybe_isnan ())
|
|
r = range_false (type);
|
|
else
|
|
r = range_true_and_false (type);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
foperator_unordered::op1_range (frange &r, tree type,
|
|
const irange &lhs,
|
|
const frange &op2,
|
|
relation_trio trio) const
|
|
{
|
|
relation_kind rel = trio.op1_op2 ();
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
// Since at least one operand must be NAN, if one of them is
|
|
// not, the other must be.
|
|
if (rel == VREL_EQ || !op2.maybe_isnan ())
|
|
r.set_nan (type);
|
|
else
|
|
r.set_varying (type);
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// A false UNORDERED means both operands are !NAN, so it's
|
|
// impossible for op2 to be a NAN.
|
|
if (op2.known_isnan ())
|
|
r.set_undefined ();
|
|
else
|
|
{
|
|
r.set_varying (type);
|
|
r.clear_nan ();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ORDERED_EXPR comparison.
|
|
|
|
class foperator_ordered : public range_operator
|
|
{
|
|
using range_operator::fold_range;
|
|
using range_operator::op1_range;
|
|
using range_operator::op2_range;
|
|
public:
|
|
bool fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio = TRIO_VARYING) const final override;
|
|
bool op1_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op2,
|
|
relation_trio = TRIO_VARYING) const final override;
|
|
bool op2_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op1,
|
|
relation_trio rel = TRIO_VARYING) const final override
|
|
{
|
|
return op1_range (r, type, lhs, op1, rel.swap_op1_op2 ());
|
|
}
|
|
} fop_ordered;
|
|
|
|
bool
|
|
foperator_ordered::fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
if (op1.known_isnan () || op2.known_isnan ())
|
|
r = range_false (type);
|
|
else if (!op1.maybe_isnan () && !op2.maybe_isnan ())
|
|
r = range_true (type);
|
|
else
|
|
r = range_true_and_false (type);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
foperator_ordered::op1_range (frange &r, tree type,
|
|
const irange &lhs,
|
|
const frange &op2,
|
|
relation_trio trio) const
|
|
{
|
|
relation_kind rel = trio.op1_op2 ();
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
// The TRUE side of ORDERED means both operands are !NAN, so
|
|
// it's impossible for op2 to be a NAN.
|
|
if (op2.known_isnan ())
|
|
r.set_undefined ();
|
|
else
|
|
{
|
|
r.set_varying (type);
|
|
r.clear_nan ();
|
|
}
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// The FALSE side of op1 ORDERED op1 implies op1 is NAN.
|
|
if (rel == VREL_EQ)
|
|
r.set_nan (type);
|
|
else
|
|
r.set_varying (type);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_negate::fold_range (frange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
if (empty_range_varying (r, type, op1, op2))
|
|
return true;
|
|
if (op1.known_isnan ())
|
|
{
|
|
bool sign;
|
|
if (op1.nan_signbit_p (sign))
|
|
r.set_nan (type, !sign);
|
|
else
|
|
r.set_nan (type);
|
|
return true;
|
|
}
|
|
|
|
REAL_VALUE_TYPE lh_lb = op1.lower_bound ();
|
|
REAL_VALUE_TYPE lh_ub = op1.upper_bound ();
|
|
lh_lb = real_value_negate (&lh_lb);
|
|
lh_ub = real_value_negate (&lh_ub);
|
|
r.set (type, lh_ub, lh_lb);
|
|
if (op1.maybe_isnan ())
|
|
{
|
|
bool sign;
|
|
if (op1.nan_signbit_p (sign))
|
|
r.update_nan (!sign);
|
|
else
|
|
r.update_nan ();
|
|
}
|
|
else
|
|
r.clear_nan ();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_negate::op1_range (frange &r, tree type,
|
|
const frange &lhs, const frange &op2,
|
|
relation_trio rel) const
|
|
{
|
|
return fold_range (r, type, lhs, op2, rel);
|
|
}
|
|
|
|
bool
|
|
operator_abs::fold_range (frange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
if (empty_range_varying (r, type, op1, op2))
|
|
return true;
|
|
if (op1.known_isnan ())
|
|
{
|
|
r.set_nan (type, /*sign=*/false);
|
|
return true;
|
|
}
|
|
|
|
const REAL_VALUE_TYPE lh_lb = op1.lower_bound ();
|
|
const REAL_VALUE_TYPE lh_ub = op1.upper_bound ();
|
|
// Handle the easy case where everything is positive.
|
|
if (real_compare (GE_EXPR, &lh_lb, &dconst0)
|
|
&& !real_iszero (&lh_lb, /*sign=*/true)
|
|
&& !op1.maybe_isnan (/*sign=*/true))
|
|
{
|
|
r = op1;
|
|
return true;
|
|
}
|
|
|
|
REAL_VALUE_TYPE min = real_value_abs (&lh_lb);
|
|
REAL_VALUE_TYPE max = real_value_abs (&lh_ub);
|
|
// If the range contains zero then we know that the minimum value in the
|
|
// range will be zero.
|
|
if (real_compare (LE_EXPR, &lh_lb, &dconst0)
|
|
&& real_compare (GE_EXPR, &lh_ub, &dconst0))
|
|
{
|
|
if (real_compare (GT_EXPR, &min, &max))
|
|
max = min;
|
|
min = dconst0;
|
|
}
|
|
else
|
|
{
|
|
// If the range was reversed, swap MIN and MAX.
|
|
if (real_compare (GT_EXPR, &min, &max))
|
|
std::swap (min, max);
|
|
}
|
|
|
|
r.set (type, min, max);
|
|
if (op1.maybe_isnan ())
|
|
r.update_nan (/*sign=*/false);
|
|
else
|
|
r.clear_nan ();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
operator_abs::op1_range (frange &r, tree type,
|
|
const frange &lhs, const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
if (empty_range_varying (r, type, lhs, op2))
|
|
return true;
|
|
if (lhs.known_isnan ())
|
|
{
|
|
r.set_nan (type);
|
|
return true;
|
|
}
|
|
|
|
// Start with the positives because negatives are an impossible result.
|
|
frange positives (type, dconst0, frange_val_max (type));
|
|
positives.update_nan (/*sign=*/false);
|
|
positives.intersect (lhs);
|
|
r = positives;
|
|
// Add -NAN if relevant.
|
|
if (r.maybe_isnan ())
|
|
{
|
|
frange neg_nan;
|
|
neg_nan.set_nan (type, true);
|
|
r.union_ (neg_nan);
|
|
}
|
|
if (r.known_isnan () || r.undefined_p ())
|
|
return true;
|
|
// Then add the negative of each pair:
|
|
// ABS(op1) = [5,20] would yield op1 => [-20,-5][5,20].
|
|
frange negatives (type, real_value_negate (&positives.upper_bound ()),
|
|
real_value_negate (&positives.lower_bound ()));
|
|
negatives.clear_nan ();
|
|
r.union_ (negatives);
|
|
return true;
|
|
}
|
|
|
|
class foperator_unordered_lt : public range_operator
|
|
{
|
|
using range_operator::fold_range;
|
|
using range_operator::op1_range;
|
|
using range_operator::op2_range;
|
|
public:
|
|
bool fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio trio = TRIO_VARYING) const final override
|
|
{
|
|
if (op1.known_isnan () || op2.known_isnan ())
|
|
{
|
|
r = range_true (type);
|
|
return true;
|
|
}
|
|
frange op1_no_nan = op1;
|
|
frange op2_no_nan = op2;
|
|
if (op1.maybe_isnan ())
|
|
op1_no_nan.clear_nan ();
|
|
if (op2.maybe_isnan ())
|
|
op2_no_nan.clear_nan ();
|
|
if (!range_op_handler (LT_EXPR).fold_range (r, type, op1_no_nan,
|
|
op2_no_nan, trio))
|
|
return false;
|
|
// The result is the same as the ordered version when the
|
|
// comparison is true or when the operands cannot be NANs.
|
|
if (!maybe_isnan (op1, op2) || r == range_true (type))
|
|
return true;
|
|
else
|
|
{
|
|
r = range_true_and_false (type);
|
|
return true;
|
|
}
|
|
}
|
|
bool op1_range (frange &r, tree type,
|
|
const irange &lhs,
|
|
const frange &op2,
|
|
relation_trio trio) const final override;
|
|
bool op2_range (frange &r, tree type,
|
|
const irange &lhs,
|
|
const frange &op1,
|
|
relation_trio trio) const final override;
|
|
} fop_unordered_lt;
|
|
|
|
bool
|
|
foperator_unordered_lt::op1_range (frange &r, tree type,
|
|
const irange &lhs,
|
|
const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
if (op2.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else if (op2.undefined_p ())
|
|
return false;
|
|
else
|
|
build_lt (r, type, op2);
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// A false UNORDERED_LT means both operands are !NAN, so it's
|
|
// impossible for op2 to be a NAN.
|
|
if (op2.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (op2.undefined_p ())
|
|
return false;
|
|
else if (build_ge (r, type, op2))
|
|
r.clear_nan ();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
foperator_unordered_lt::op2_range (frange &r, tree type,
|
|
const irange &lhs,
|
|
const frange &op1,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
if (op1.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else if (op1.undefined_p ())
|
|
return false;
|
|
else
|
|
build_gt (r, type, op1);
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// A false UNORDERED_LT means both operands are !NAN, so it's
|
|
// impossible for op1 to be a NAN.
|
|
if (op1.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (op1.undefined_p ())
|
|
return false;
|
|
else if (build_le (r, type, op1))
|
|
r.clear_nan ();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
class foperator_unordered_le : public range_operator
|
|
{
|
|
using range_operator::fold_range;
|
|
using range_operator::op1_range;
|
|
using range_operator::op2_range;
|
|
public:
|
|
bool fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio trio = TRIO_VARYING) const final override
|
|
{
|
|
if (op1.known_isnan () || op2.known_isnan ())
|
|
{
|
|
r = range_true (type);
|
|
return true;
|
|
}
|
|
frange op1_no_nan = op1;
|
|
frange op2_no_nan = op2;
|
|
if (op1.maybe_isnan ())
|
|
op1_no_nan.clear_nan ();
|
|
if (op2.maybe_isnan ())
|
|
op2_no_nan.clear_nan ();
|
|
if (!range_op_handler (LE_EXPR).fold_range (r, type, op1_no_nan,
|
|
op2_no_nan, trio))
|
|
return false;
|
|
// The result is the same as the ordered version when the
|
|
// comparison is true or when the operands cannot be NANs.
|
|
if (!maybe_isnan (op1, op2) || r == range_true (type))
|
|
return true;
|
|
else
|
|
{
|
|
r = range_true_and_false (type);
|
|
return true;
|
|
}
|
|
}
|
|
bool op1_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op2,
|
|
relation_trio = TRIO_VARYING) const final override;
|
|
bool op2_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op1,
|
|
relation_trio = TRIO_VARYING) const final override;
|
|
} fop_unordered_le;
|
|
|
|
bool
|
|
foperator_unordered_le::op1_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
if (op2.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else if (op2.undefined_p ())
|
|
return false;
|
|
else
|
|
build_le (r, type, op2);
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// A false UNORDERED_LE means both operands are !NAN, so it's
|
|
// impossible for op2 to be a NAN.
|
|
if (op2.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (build_gt (r, type, op2))
|
|
r.clear_nan ();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
foperator_unordered_le::op2_range (frange &r,
|
|
tree type,
|
|
const irange &lhs,
|
|
const frange &op1,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
if (op1.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else if (op1.undefined_p ())
|
|
return false;
|
|
else
|
|
build_ge (r, type, op1);
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// A false UNORDERED_LE means both operands are !NAN, so it's
|
|
// impossible for op1 to be a NAN.
|
|
if (op1.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (op1.undefined_p ())
|
|
return false;
|
|
else if (build_lt (r, type, op1))
|
|
r.clear_nan ();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
class foperator_unordered_gt : public range_operator
|
|
{
|
|
using range_operator::fold_range;
|
|
using range_operator::op1_range;
|
|
using range_operator::op2_range;
|
|
public:
|
|
bool fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio trio = TRIO_VARYING) const final override
|
|
{
|
|
if (op1.known_isnan () || op2.known_isnan ())
|
|
{
|
|
r = range_true (type);
|
|
return true;
|
|
}
|
|
frange op1_no_nan = op1;
|
|
frange op2_no_nan = op2;
|
|
if (op1.maybe_isnan ())
|
|
op1_no_nan.clear_nan ();
|
|
if (op2.maybe_isnan ())
|
|
op2_no_nan.clear_nan ();
|
|
if (!range_op_handler (GT_EXPR).fold_range (r, type, op1_no_nan,
|
|
op2_no_nan, trio))
|
|
return false;
|
|
// The result is the same as the ordered version when the
|
|
// comparison is true or when the operands cannot be NANs.
|
|
if (!maybe_isnan (op1, op2) || r == range_true (type))
|
|
return true;
|
|
else
|
|
{
|
|
r = range_true_and_false (type);
|
|
return true;
|
|
}
|
|
}
|
|
bool op1_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op2,
|
|
relation_trio = TRIO_VARYING) const final override;
|
|
bool op2_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op1,
|
|
relation_trio = TRIO_VARYING) const final override;
|
|
} fop_unordered_gt;
|
|
|
|
bool
|
|
foperator_unordered_gt::op1_range (frange &r,
|
|
tree type,
|
|
const irange &lhs,
|
|
const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
if (op2.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else if (op2.undefined_p ())
|
|
return false;
|
|
else
|
|
build_gt (r, type, op2);
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// A false UNORDERED_GT means both operands are !NAN, so it's
|
|
// impossible for op2 to be a NAN.
|
|
if (op2.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (op2.undefined_p ())
|
|
return false;
|
|
else if (build_le (r, type, op2))
|
|
r.clear_nan ();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
foperator_unordered_gt::op2_range (frange &r,
|
|
tree type,
|
|
const irange &lhs,
|
|
const frange &op1,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
if (op1.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else if (op1.undefined_p ())
|
|
return false;
|
|
else
|
|
build_lt (r, type, op1);
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// A false UNORDERED_GT means both operands are !NAN, so it's
|
|
// impossible for op1 to be a NAN.
|
|
if (op1.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (op1.undefined_p ())
|
|
return false;
|
|
else if (build_ge (r, type, op1))
|
|
r.clear_nan ();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
class foperator_unordered_ge : public range_operator
|
|
{
|
|
using range_operator::fold_range;
|
|
using range_operator::op1_range;
|
|
using range_operator::op2_range;
|
|
public:
|
|
bool fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio trio = TRIO_VARYING) const final override
|
|
{
|
|
if (op1.known_isnan () || op2.known_isnan ())
|
|
{
|
|
r = range_true (type);
|
|
return true;
|
|
}
|
|
frange op1_no_nan = op1;
|
|
frange op2_no_nan = op2;
|
|
if (op1.maybe_isnan ())
|
|
op1_no_nan.clear_nan ();
|
|
if (op2.maybe_isnan ())
|
|
op2_no_nan.clear_nan ();
|
|
if (!range_op_handler (GE_EXPR).fold_range (r, type, op1_no_nan,
|
|
op2_no_nan, trio))
|
|
return false;
|
|
// The result is the same as the ordered version when the
|
|
// comparison is true or when the operands cannot be NANs.
|
|
if (!maybe_isnan (op1, op2) || r == range_true (type))
|
|
return true;
|
|
else
|
|
{
|
|
r = range_true_and_false (type);
|
|
return true;
|
|
}
|
|
}
|
|
bool op1_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op2,
|
|
relation_trio = TRIO_VARYING) const final override;
|
|
bool op2_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op1,
|
|
relation_trio = TRIO_VARYING) const final override;
|
|
} fop_unordered_ge;
|
|
|
|
bool
|
|
foperator_unordered_ge::op1_range (frange &r,
|
|
tree type,
|
|
const irange &lhs,
|
|
const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
if (op2.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else if (op2.undefined_p ())
|
|
return false;
|
|
else
|
|
build_ge (r, type, op2);
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// A false UNORDERED_GE means both operands are !NAN, so it's
|
|
// impossible for op2 to be a NAN.
|
|
if (op2.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (op2.undefined_p ())
|
|
return false;
|
|
else if (build_lt (r, type, op2))
|
|
r.clear_nan ();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
foperator_unordered_ge::op2_range (frange &r, tree type,
|
|
const irange &lhs,
|
|
const frange &op1,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
if (op1.maybe_isnan ())
|
|
r.set_varying (type);
|
|
else if (op1.undefined_p ())
|
|
return false;
|
|
else
|
|
build_le (r, type, op1);
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// A false UNORDERED_GE means both operands are !NAN, so it's
|
|
// impossible for op1 to be a NAN.
|
|
if (op1.known_isnan ())
|
|
r.set_undefined ();
|
|
else if (op1.undefined_p ())
|
|
return false;
|
|
else if (build_gt (r, type, op1))
|
|
r.clear_nan ();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
class foperator_unordered_equal : public range_operator
|
|
{
|
|
using range_operator::fold_range;
|
|
using range_operator::op1_range;
|
|
using range_operator::op2_range;
|
|
public:
|
|
bool fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio trio = TRIO_VARYING) const final override
|
|
{
|
|
if (op1.known_isnan () || op2.known_isnan ())
|
|
{
|
|
r = range_true (type);
|
|
return true;
|
|
}
|
|
frange op1_no_nan = op1;
|
|
frange op2_no_nan = op2;
|
|
if (op1.maybe_isnan ())
|
|
op1_no_nan.clear_nan ();
|
|
if (op2.maybe_isnan ())
|
|
op2_no_nan.clear_nan ();
|
|
if (!range_op_handler (EQ_EXPR).fold_range (r, type, op1_no_nan,
|
|
op2_no_nan, trio))
|
|
return false;
|
|
// The result is the same as the ordered version when the
|
|
// comparison is true or when the operands cannot be NANs.
|
|
if (!maybe_isnan (op1, op2) || r == range_true (type))
|
|
return true;
|
|
else
|
|
{
|
|
r = range_true_and_false (type);
|
|
return true;
|
|
}
|
|
}
|
|
bool op1_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op2,
|
|
relation_trio = TRIO_VARYING) const final override;
|
|
bool op2_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op1,
|
|
relation_trio rel = TRIO_VARYING) const final override
|
|
{
|
|
return op1_range (r, type, lhs, op1, rel.swap_op1_op2 ());
|
|
}
|
|
} fop_unordered_equal;
|
|
|
|
bool
|
|
foperator_unordered_equal::op1_range (frange &r, tree type,
|
|
const irange &lhs,
|
|
const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
// If it's true, the result is the same as OP2 plus a NAN.
|
|
r = op2;
|
|
// Add both zeros if there's the possibility of zero equality.
|
|
frange_add_zeros (r, type);
|
|
// Add the possibility of a NAN.
|
|
r.update_nan ();
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// A false UNORDERED_EQ means both operands are !NAN, so it's
|
|
// impossible for op2 to be a NAN.
|
|
if (op2.known_isnan ())
|
|
r.set_undefined ();
|
|
else
|
|
{
|
|
// The false side indicates !NAN and not equal. We can at least
|
|
// represent !NAN.
|
|
r.set_varying (type);
|
|
r.clear_nan ();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
class foperator_ltgt : public range_operator
|
|
{
|
|
using range_operator::fold_range;
|
|
using range_operator::op1_range;
|
|
using range_operator::op2_range;
|
|
public:
|
|
bool fold_range (irange &r, tree type,
|
|
const frange &op1, const frange &op2,
|
|
relation_trio trio = TRIO_VARYING) const final override
|
|
{
|
|
if (op1.known_isnan () || op2.known_isnan ())
|
|
{
|
|
r = range_false (type);
|
|
return true;
|
|
}
|
|
frange op1_no_nan = op1;
|
|
frange op2_no_nan = op2;
|
|
if (op1.maybe_isnan ())
|
|
op1_no_nan.clear_nan ();
|
|
if (op2.maybe_isnan ())
|
|
op2_no_nan.clear_nan ();
|
|
if (!range_op_handler (NE_EXPR).fold_range (r, type, op1_no_nan,
|
|
op2_no_nan, trio))
|
|
return false;
|
|
// The result is the same as the ordered version when the
|
|
// comparison is true or when the operands cannot be NANs.
|
|
if (!maybe_isnan (op1, op2) || r == range_false (type))
|
|
return true;
|
|
else
|
|
{
|
|
r = range_true_and_false (type);
|
|
return true;
|
|
}
|
|
}
|
|
bool op1_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op2,
|
|
relation_trio = TRIO_VARYING) const final override;
|
|
bool op2_range (frange &r, tree type,
|
|
const irange &lhs, const frange &op1,
|
|
relation_trio rel = TRIO_VARYING) const final override
|
|
{
|
|
return op1_range (r, type, lhs, op1, rel.swap_op1_op2 ());
|
|
}
|
|
} fop_ltgt;
|
|
|
|
bool
|
|
foperator_ltgt::op1_range (frange &r, tree type,
|
|
const irange &lhs,
|
|
const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
switch (get_bool_state (r, lhs, type))
|
|
{
|
|
case BRS_TRUE:
|
|
// A true LTGT means both operands are !NAN, so it's
|
|
// impossible for op2 to be a NAN.
|
|
if (op2.known_isnan ())
|
|
r.set_undefined ();
|
|
else
|
|
{
|
|
// The true side indicates !NAN and not equal. We can at least
|
|
// represent !NAN.
|
|
r.set_varying (type);
|
|
r.clear_nan ();
|
|
}
|
|
break;
|
|
|
|
case BRS_FALSE:
|
|
// If it's false, the result is the same as OP2 plus a NAN.
|
|
r = op2;
|
|
// Add both zeros if there's the possibility of zero equality.
|
|
frange_add_zeros (r, type);
|
|
// Add the possibility of a NAN.
|
|
r.update_nan ();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Final tweaks for float binary op op1_range/op2_range.
|
|
// Return TRUE if the operation is performed and a valid range is available.
|
|
|
|
static bool
|
|
float_binary_op_range_finish (bool ret, frange &r, tree type,
|
|
const frange &lhs, bool div_op2 = false)
|
|
{
|
|
if (!ret)
|
|
return false;
|
|
|
|
// If we get a known NAN from reverse op, it means either that
|
|
// the other operand was known NAN (in that case we know nothing),
|
|
// or the reverse operation introduced a known NAN.
|
|
// Say for lhs = op1 * op2 if lhs is [-0, +0] and op2 is too,
|
|
// 0 / 0 is known NAN. Just punt in that case.
|
|
// If NANs aren't honored, we get for 0 / 0 UNDEFINED, so punt as well.
|
|
// Or if lhs is a known NAN, we also don't know anything.
|
|
if (r.known_isnan () || lhs.known_isnan () || r.undefined_p ())
|
|
{
|
|
r.set_varying (type);
|
|
return true;
|
|
}
|
|
|
|
// 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 ();
|
|
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
|
|
r.update_nan ();
|
|
return true;
|
|
}
|
|
|
|
// True if [lb, ub] is [+-0, +-0].
|
|
static bool
|
|
zero_p (const REAL_VALUE_TYPE &lb, const REAL_VALUE_TYPE &ub)
|
|
{
|
|
return real_iszero (&lb) && real_iszero (&ub);
|
|
}
|
|
|
|
// True if +0 or -0 is in [lb, ub] range.
|
|
static bool
|
|
contains_zero_p (const REAL_VALUE_TYPE &lb, const REAL_VALUE_TYPE &ub)
|
|
{
|
|
return (real_compare (LE_EXPR, &lb, &dconst0)
|
|
&& real_compare (GE_EXPR, &ub, &dconst0));
|
|
}
|
|
|
|
// True if [lb, ub] is [-INF, -INF] or [+INF, +INF].
|
|
static bool
|
|
singleton_inf_p (const REAL_VALUE_TYPE &lb, const REAL_VALUE_TYPE &ub)
|
|
{
|
|
return real_isinf (&lb) && real_isinf (&ub, real_isneg (&lb));
|
|
}
|
|
|
|
// Return -1 if binary op result must have sign bit set,
|
|
// 1 if binary op result must have sign bit clear,
|
|
// 0 otherwise.
|
|
// Sign bit of binary op result is exclusive or of the
|
|
// operand's sign bits.
|
|
static int
|
|
signbit_known_p (const REAL_VALUE_TYPE &lh_lb, const REAL_VALUE_TYPE &lh_ub,
|
|
const REAL_VALUE_TYPE &rh_lb, const REAL_VALUE_TYPE &rh_ub)
|
|
{
|
|
if (real_isneg (&lh_lb) == real_isneg (&lh_ub)
|
|
&& real_isneg (&rh_lb) == real_isneg (&rh_ub))
|
|
{
|
|
if (real_isneg (&lh_lb) == real_isneg (&rh_ub))
|
|
return 1;
|
|
else
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Set [lb, ub] to [-0, -0], [-0, +0] or [+0, +0] depending on
|
|
// signbit_known.
|
|
static void
|
|
zero_range (REAL_VALUE_TYPE &lb, REAL_VALUE_TYPE &ub, int signbit_known)
|
|
{
|
|
ub = lb = dconst0;
|
|
if (signbit_known <= 0)
|
|
lb = dconstm0;
|
|
if (signbit_known < 0)
|
|
ub = lb;
|
|
}
|
|
|
|
// Set [lb, ub] to [-INF, -INF], [-INF, +INF] or [+INF, +INF] depending on
|
|
// signbit_known.
|
|
static void
|
|
inf_range (REAL_VALUE_TYPE &lb, REAL_VALUE_TYPE &ub, int signbit_known)
|
|
{
|
|
if (signbit_known > 0)
|
|
ub = lb = dconstinf;
|
|
else if (signbit_known < 0)
|
|
ub = lb = dconstninf;
|
|
else
|
|
{
|
|
lb = dconstninf;
|
|
ub = dconstinf;
|
|
}
|
|
}
|
|
|
|
// Set [lb, ub] to [-INF, -0], [-INF, +INF] or [+0, +INF] depending on
|
|
// signbit_known.
|
|
static void
|
|
zero_to_inf_range (REAL_VALUE_TYPE &lb, REAL_VALUE_TYPE &ub, int signbit_known)
|
|
{
|
|
if (signbit_known > 0)
|
|
{
|
|
lb = dconst0;
|
|
ub = dconstinf;
|
|
}
|
|
else if (signbit_known < 0)
|
|
{
|
|
lb = dconstninf;
|
|
ub = dconstm0;
|
|
}
|
|
else
|
|
{
|
|
lb = dconstninf;
|
|
ub = dconstinf;
|
|
}
|
|
}
|
|
|
|
/* Extend the LHS range by 1ulp in each direction. For op1_range
|
|
or op2_range of binary operations just computing the inverse
|
|
operation on ranges isn't sufficient. Consider e.g.
|
|
[1., 1.] = op1 + [1., 1.]. op1's range is not [0., 0.], but
|
|
[-0x1.0p-54, 0x1.0p-53] (when not -frounding-math), any value for
|
|
which adding 1. to it results in 1. after rounding to nearest.
|
|
So, for op1_range/op2_range extend the lhs range by 1ulp (or 0.5ulp)
|
|
in each direction. See PR109008 for more details. */
|
|
|
|
static frange
|
|
float_widen_lhs_range (tree type, const frange &lhs)
|
|
{
|
|
frange ret = lhs;
|
|
if (lhs.known_isnan ())
|
|
return ret;
|
|
REAL_VALUE_TYPE lb = lhs.lower_bound ();
|
|
REAL_VALUE_TYPE ub = lhs.upper_bound ();
|
|
if (real_isfinite (&lb))
|
|
{
|
|
frange_nextafter (TYPE_MODE (type), lb, dconstninf);
|
|
if (real_isinf (&lb))
|
|
{
|
|
/* For -DBL_MAX, instead of -Inf use
|
|
nexttoward (-DBL_MAX, -LDBL_MAX) in a hypothetical
|
|
wider type with the same mantissa precision but larger
|
|
exponent range; it is outside of range of double values,
|
|
but makes it clear it is just one ulp larger rather than
|
|
infinite amount larger. */
|
|
lb = dconstm1;
|
|
SET_REAL_EXP (&lb, FLOAT_MODE_FORMAT (TYPE_MODE (type))->emax + 1);
|
|
}
|
|
if (!flag_rounding_math && !MODE_COMPOSITE_P (TYPE_MODE (type)))
|
|
{
|
|
/* If not -frounding-math nor IBM double double, actually widen
|
|
just by 0.5ulp rather than 1ulp. */
|
|
REAL_VALUE_TYPE tem;
|
|
real_arithmetic (&tem, PLUS_EXPR, &lhs.lower_bound (), &lb);
|
|
real_arithmetic (&lb, RDIV_EXPR, &tem, &dconst2);
|
|
}
|
|
}
|
|
if (real_isfinite (&ub))
|
|
{
|
|
frange_nextafter (TYPE_MODE (type), ub, dconstinf);
|
|
if (real_isinf (&ub))
|
|
{
|
|
/* For DBL_MAX similarly. */
|
|
ub = dconst1;
|
|
SET_REAL_EXP (&ub, FLOAT_MODE_FORMAT (TYPE_MODE (type))->emax + 1);
|
|
}
|
|
if (!flag_rounding_math && !MODE_COMPOSITE_P (TYPE_MODE (type)))
|
|
{
|
|
/* If not -frounding-math nor IBM double double, actually widen
|
|
just by 0.5ulp rather than 1ulp. */
|
|
REAL_VALUE_TYPE tem;
|
|
real_arithmetic (&tem, PLUS_EXPR, &lhs.upper_bound (), &ub);
|
|
real_arithmetic (&ub, RDIV_EXPR, &tem, &dconst2);
|
|
}
|
|
}
|
|
/* Temporarily disable -ffinite-math-only, so that frange::set doesn't
|
|
reduce the range back to real_min_representable (type) as lower bound
|
|
or real_max_representable (type) as upper bound. */
|
|
bool save_flag_finite_math_only = flag_finite_math_only;
|
|
flag_finite_math_only = false;
|
|
ret.set (type, lb, ub, lhs.get_nan_state ());
|
|
flag_finite_math_only = save_flag_finite_math_only;
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
operator_plus::op1_range (frange &r, tree type, const frange &lhs,
|
|
const frange &op2, relation_trio) const
|
|
{
|
|
if (lhs.undefined_p ())
|
|
return false;
|
|
range_op_handler minus (MINUS_EXPR);
|
|
if (!minus)
|
|
return false;
|
|
frange wlhs = float_widen_lhs_range (type, lhs);
|
|
return float_binary_op_range_finish (minus.fold_range (r, type, wlhs, op2),
|
|
r, type, wlhs);
|
|
}
|
|
|
|
bool
|
|
operator_plus::op2_range (frange &r, tree type,
|
|
const frange &lhs, const frange &op1,
|
|
relation_trio) const
|
|
{
|
|
return op1_range (r, type, lhs, op1);
|
|
}
|
|
|
|
void
|
|
operator_plus::rv_fold (frange &r, tree type,
|
|
const REAL_VALUE_TYPE &lh_lb,
|
|
const REAL_VALUE_TYPE &lh_ub,
|
|
const REAL_VALUE_TYPE &rh_lb,
|
|
const REAL_VALUE_TYPE &rh_ub,
|
|
relation_kind) const
|
|
{
|
|
REAL_VALUE_TYPE lb, ub;
|
|
bool maybe_nan = false;
|
|
|
|
frange_arithmetic (PLUS_EXPR, type, lb, lh_lb, rh_lb, dconstninf);
|
|
frange_arithmetic (PLUS_EXPR, type, ub, lh_ub, rh_ub, dconstinf);
|
|
|
|
// [-INF] + [+INF] = NAN
|
|
if (real_isinf (&lh_lb, true) && real_isinf (&rh_ub, false))
|
|
maybe_nan = true;
|
|
// [+INF] + [-INF] = NAN
|
|
else if (real_isinf (&lh_ub, false) && real_isinf (&rh_lb, true))
|
|
maybe_nan = true;
|
|
|
|
// Handle possible NANs by saturating to the appropriate INF if only
|
|
// one end is a NAN. If both ends are a NAN, just return a NAN.
|
|
bool lb_nan = real_isnan (&lb);
|
|
bool ub_nan = real_isnan (&ub);
|
|
if (lb_nan && ub_nan)
|
|
{
|
|
r.set_nan (type);
|
|
return;
|
|
}
|
|
if (lb_nan)
|
|
lb = dconstninf;
|
|
else if (ub_nan)
|
|
ub = dconstinf;
|
|
r.set (type, lb, ub, nan_state (maybe_nan));
|
|
}
|
|
|
|
|
|
bool
|
|
operator_minus::op1_range (frange &r, tree type,
|
|
const frange &lhs, const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
if (lhs.undefined_p ())
|
|
return false;
|
|
frange wlhs = float_widen_lhs_range (type, lhs);
|
|
return float_binary_op_range_finish (
|
|
range_op_handler (PLUS_EXPR).fold_range (r, type, wlhs, op2),
|
|
r, type, wlhs);
|
|
}
|
|
|
|
bool
|
|
operator_minus::op2_range (frange &r, tree type,
|
|
const frange &lhs, const frange &op1,
|
|
relation_trio) const
|
|
{
|
|
if (lhs.undefined_p ())
|
|
return false;
|
|
frange wlhs = float_widen_lhs_range (type, lhs);
|
|
return float_binary_op_range_finish (fold_range (r, type, op1, wlhs),
|
|
r, type, wlhs);
|
|
}
|
|
|
|
void
|
|
operator_minus::rv_fold (frange &r, tree type,
|
|
const REAL_VALUE_TYPE &lh_lb,
|
|
const REAL_VALUE_TYPE &lh_ub,
|
|
const REAL_VALUE_TYPE &rh_lb,
|
|
const REAL_VALUE_TYPE &rh_ub,
|
|
relation_kind) const
|
|
{
|
|
REAL_VALUE_TYPE lb, ub;
|
|
bool maybe_nan = false;
|
|
|
|
frange_arithmetic (MINUS_EXPR, type, lb, lh_lb, rh_ub, dconstninf);
|
|
frange_arithmetic (MINUS_EXPR, type, ub, lh_ub, rh_lb, dconstinf);
|
|
|
|
// [+INF] - [+INF] = NAN
|
|
if (real_isinf (&lh_ub, false) && real_isinf (&rh_ub, false))
|
|
maybe_nan = true;
|
|
// [-INF] - [-INF] = NAN
|
|
else if (real_isinf (&lh_lb, true) && real_isinf (&rh_lb, true))
|
|
maybe_nan = true;
|
|
|
|
// Handle possible NANs by saturating to the appropriate INF if only
|
|
// one end is a NAN. If both ends are a NAN, just return a NAN.
|
|
bool lb_nan = real_isnan (&lb);
|
|
bool ub_nan = real_isnan (&ub);
|
|
if (lb_nan && ub_nan)
|
|
{
|
|
r.set_nan (type);
|
|
return;
|
|
}
|
|
if (lb_nan)
|
|
lb = dconstninf;
|
|
else if (ub_nan)
|
|
ub = dconstinf;
|
|
r.set (type, lb, ub, nan_state (maybe_nan));
|
|
}
|
|
|
|
|
|
// Given CP[0] to CP[3] floating point values rounded to -INF,
|
|
// set LB to the smallest of them (treating -0 as smaller to +0).
|
|
// Given CP[4] to CP[7] floating point values rounded to +INF,
|
|
// set UB to the largest of them (treating -0 as smaller to +0).
|
|
|
|
static void
|
|
find_range (REAL_VALUE_TYPE &lb, REAL_VALUE_TYPE &ub,
|
|
const REAL_VALUE_TYPE (&cp)[8])
|
|
{
|
|
lb = cp[0];
|
|
ub = cp[4];
|
|
for (int i = 1; i < 4; ++i)
|
|
{
|
|
if (real_less (&cp[i], &lb)
|
|
|| (real_iszero (&lb) && real_isnegzero (&cp[i])))
|
|
lb = cp[i];
|
|
if (real_less (&ub, &cp[i + 4])
|
|
|| (real_isnegzero (&ub) && real_iszero (&cp[i + 4])))
|
|
ub = cp[i + 4];
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
operator_mult::op1_range (frange &r, tree type,
|
|
const frange &lhs, const frange &op2,
|
|
relation_trio) const
|
|
{
|
|
if (lhs.undefined_p ())
|
|
return false;
|
|
range_op_handler rdiv (RDIV_EXPR);
|
|
if (!rdiv)
|
|
return false;
|
|
frange wlhs = float_widen_lhs_range (type, lhs);
|
|
bool ret = rdiv.fold_range (r, type, wlhs, op2);
|
|
if (ret == false)
|
|
return false;
|
|
if (wlhs.known_isnan () || op2.known_isnan () || op2.undefined_p ())
|
|
return float_binary_op_range_finish (ret, r, type, wlhs);
|
|
const REAL_VALUE_TYPE &lhs_lb = wlhs.lower_bound ();
|
|
const REAL_VALUE_TYPE &lhs_ub = wlhs.upper_bound ();
|
|
const REAL_VALUE_TYPE &op2_lb = op2.lower_bound ();
|
|
const REAL_VALUE_TYPE &op2_ub = op2.upper_bound ();
|
|
if ((contains_zero_p (lhs_lb, lhs_ub) && contains_zero_p (op2_lb, op2_ub))
|
|
|| ((real_isinf (&lhs_lb) || real_isinf (&lhs_ub))
|
|
&& (real_isinf (&op2_lb) || real_isinf (&op2_ub))))
|
|
{
|
|
// If both lhs and op2 could be zeros or both could be infinities,
|
|
// we don't know anything about op1 except maybe for the sign
|
|
// and perhaps if it can be NAN or not.
|
|
REAL_VALUE_TYPE lb, ub;
|
|
int signbit_known = signbit_known_p (lhs_lb, lhs_ub, op2_lb, op2_ub);
|
|
zero_to_inf_range (lb, ub, signbit_known);
|
|
r.set (type, lb, ub);
|
|
}
|
|
// Otherwise, if op2 is a singleton INF and lhs doesn't include INF,
|
|
// 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, wlhs);
|
|
}
|
|
|
|
bool
|
|
operator_mult::op2_range (frange &r, tree type,
|
|
const frange &lhs, const frange &op1,
|
|
relation_trio) const
|
|
{
|
|
return op1_range (r, type, lhs, op1);
|
|
}
|
|
|
|
void
|
|
operator_mult::rv_fold (frange &r, tree type,
|
|
const REAL_VALUE_TYPE &lh_lb,
|
|
const REAL_VALUE_TYPE &lh_ub,
|
|
const REAL_VALUE_TYPE &rh_lb,
|
|
const REAL_VALUE_TYPE &rh_ub,
|
|
relation_kind kind) const
|
|
{
|
|
bool is_square
|
|
= (kind == VREL_EQ
|
|
&& real_equal (&lh_lb, &rh_lb)
|
|
&& real_equal (&lh_ub, &rh_ub)
|
|
&& real_isneg (&lh_lb) == real_isneg (&rh_lb)
|
|
&& real_isneg (&lh_ub) == real_isneg (&rh_ub));
|
|
REAL_VALUE_TYPE lb, ub;
|
|
bool maybe_nan = false;
|
|
// x * x never produces a new NAN and we only multiply the same
|
|
// values, so the 0 * INF problematic cases never appear there.
|
|
if (!is_square)
|
|
{
|
|
// [+-0, +-0] * [+INF,+INF] (or [-INF,-INF] or swapped is a known NAN.
|
|
if ((zero_p (lh_lb, lh_ub) && singleton_inf_p (rh_lb, rh_ub))
|
|
|| (zero_p (rh_lb, rh_ub) && singleton_inf_p (lh_lb, lh_ub)))
|
|
{
|
|
r.set_nan (type);
|
|
return;
|
|
}
|
|
|
|
// Otherwise, if one range includes zero and the other ends with +-INF,
|
|
// it is a maybe NAN.
|
|
if ((contains_zero_p (lh_lb, lh_ub)
|
|
&& (real_isinf (&rh_lb) || real_isinf (&rh_ub)))
|
|
|| (contains_zero_p (rh_lb, rh_ub)
|
|
&& (real_isinf (&lh_lb) || real_isinf (&lh_ub))))
|
|
{
|
|
maybe_nan = true;
|
|
|
|
int signbit_known = signbit_known_p (lh_lb, lh_ub, rh_lb, rh_ub);
|
|
|
|
// If one of the ranges that includes INF is singleton
|
|
// and the other range includes zero, the resulting
|
|
// range is INF and NAN, because the 0 * INF boundary
|
|
// case will be NAN, but already nextafter (0, 1) * INF
|
|
// is INF.
|
|
if (singleton_inf_p (lh_lb, lh_ub)
|
|
|| singleton_inf_p (rh_lb, rh_ub))
|
|
{
|
|
inf_range (lb, ub, signbit_known);
|
|
r.set (type, lb, ub, nan_state (true));
|
|
return;
|
|
}
|
|
|
|
// If one of the multiplicands must be zero, the resulting
|
|
// range is +-0 and NAN.
|
|
if (zero_p (lh_lb, lh_ub) || zero_p (rh_lb, rh_ub))
|
|
{
|
|
zero_range (lb, ub, signbit_known);
|
|
r.set (type, lb, ub, nan_state (true));
|
|
return;
|
|
}
|
|
|
|
// Otherwise one of the multiplicands could be
|
|
// [0.0, nextafter (0.0, 1.0)] and the [DBL_MAX, INF]
|
|
// or similarly with different signs. 0.0 * DBL_MAX
|
|
// is still 0.0, nextafter (0.0, 1.0) * INF is still INF,
|
|
// so if the signs are always the same or always different,
|
|
// result is [+0.0, +INF] or [-INF, -0.0], otherwise VARYING.
|
|
zero_to_inf_range (lb, ub, signbit_known);
|
|
r.set (type, lb, ub, nan_state (true));
|
|
return;
|
|
}
|
|
}
|
|
|
|
REAL_VALUE_TYPE cp[8];
|
|
// Do a cross-product. At this point none of the multiplications
|
|
// should produce a NAN.
|
|
frange_arithmetic (MULT_EXPR, type, cp[0], lh_lb, rh_lb, dconstninf);
|
|
frange_arithmetic (MULT_EXPR, type, cp[4], lh_lb, rh_lb, dconstinf);
|
|
if (is_square)
|
|
{
|
|
// For x * x we can just do max (lh_lb * lh_lb, lh_ub * lh_ub)
|
|
// as maximum and -0.0 as minimum if 0.0 is in the range,
|
|
// otherwise min (lh_lb * lh_lb, lh_ub * lh_ub).
|
|
// -0.0 rather than 0.0 because VREL_EQ doesn't prove that
|
|
// x and y are bitwise equal, just that they compare equal.
|
|
if (contains_zero_p (lh_lb, lh_ub))
|
|
{
|
|
if (real_isneg (&lh_lb) == real_isneg (&lh_ub))
|
|
cp[1] = dconst0;
|
|
else
|
|
cp[1] = dconstm0;
|
|
}
|
|
else
|
|
cp[1] = cp[0];
|
|
cp[2] = cp[0];
|
|
cp[5] = cp[4];
|
|
cp[6] = cp[4];
|
|
}
|
|
else
|
|
{
|
|
frange_arithmetic (MULT_EXPR, type, cp[1], lh_lb, rh_ub, dconstninf);
|
|
frange_arithmetic (MULT_EXPR, type, cp[5], lh_lb, rh_ub, dconstinf);
|
|
frange_arithmetic (MULT_EXPR, type, cp[2], lh_ub, rh_lb, dconstninf);
|
|
frange_arithmetic (MULT_EXPR, type, cp[6], lh_ub, rh_lb, dconstinf);
|
|
}
|
|
frange_arithmetic (MULT_EXPR, type, cp[3], lh_ub, rh_ub, dconstninf);
|
|
frange_arithmetic (MULT_EXPR, type, cp[7], lh_ub, rh_ub, dconstinf);
|
|
|
|
find_range (lb, ub, cp);
|
|
|
|
gcc_checking_assert (!real_isnan (&lb));
|
|
gcc_checking_assert (!real_isnan (&ub));
|
|
r.set (type, lb, ub, nan_state (maybe_nan));
|
|
}
|
|
|
|
|
|
class foperator_div : public range_operator
|
|
{
|
|
using range_operator::op1_range;
|
|
using range_operator::op2_range;
|
|
public:
|
|
virtual bool op1_range (frange &r, tree type,
|
|
const frange &lhs,
|
|
const frange &op2,
|
|
relation_trio = TRIO_VARYING) const final override
|
|
{
|
|
if (lhs.undefined_p ())
|
|
return false;
|
|
frange wlhs = float_widen_lhs_range (type, lhs);
|
|
bool ret = range_op_handler (MULT_EXPR).fold_range (r, type, wlhs, op2);
|
|
if (!ret)
|
|
return ret;
|
|
if (wlhs.known_isnan () || op2.known_isnan () || op2.undefined_p ())
|
|
return float_binary_op_range_finish (ret, r, type, wlhs);
|
|
const REAL_VALUE_TYPE &lhs_lb = wlhs.lower_bound ();
|
|
const REAL_VALUE_TYPE &lhs_ub = wlhs.upper_bound ();
|
|
const REAL_VALUE_TYPE &op2_lb = op2.lower_bound ();
|
|
const REAL_VALUE_TYPE &op2_ub = op2.upper_bound ();
|
|
if ((contains_zero_p (lhs_lb, lhs_ub)
|
|
&& (real_isinf (&op2_lb) || real_isinf (&op2_ub)))
|
|
|| ((contains_zero_p (op2_lb, op2_ub))
|
|
&& (real_isinf (&lhs_lb) || real_isinf (&lhs_ub))))
|
|
{
|
|
// If both lhs could be zero and op2 infinity or vice versa,
|
|
// we don't know anything about op1 except maybe for the sign
|
|
// and perhaps if it can be NAN or not.
|
|
REAL_VALUE_TYPE lb, ub;
|
|
int signbit_known = signbit_known_p (lhs_lb, lhs_ub, op2_lb, op2_ub);
|
|
zero_to_inf_range (lb, ub, signbit_known);
|
|
r.set (type, lb, ub);
|
|
}
|
|
return float_binary_op_range_finish (ret, r, type, wlhs);
|
|
}
|
|
virtual bool op2_range (frange &r, tree type,
|
|
const frange &lhs,
|
|
const frange &op1,
|
|
relation_trio = TRIO_VARYING) const final override
|
|
{
|
|
if (lhs.undefined_p ())
|
|
return false;
|
|
frange wlhs = float_widen_lhs_range (type, lhs);
|
|
bool ret = fold_range (r, type, op1, wlhs);
|
|
if (!ret)
|
|
return ret;
|
|
if (wlhs.known_isnan () || op1.known_isnan () || op1.undefined_p ())
|
|
return float_binary_op_range_finish (ret, r, type, wlhs, true);
|
|
const REAL_VALUE_TYPE &lhs_lb = wlhs.lower_bound ();
|
|
const REAL_VALUE_TYPE &lhs_ub = wlhs.upper_bound ();
|
|
const REAL_VALUE_TYPE &op1_lb = op1.lower_bound ();
|
|
const REAL_VALUE_TYPE &op1_ub = op1.upper_bound ();
|
|
if ((contains_zero_p (lhs_lb, lhs_ub) && contains_zero_p (op1_lb, op1_ub))
|
|
|| ((real_isinf (&lhs_lb) || real_isinf (&lhs_ub))
|
|
&& (real_isinf (&op1_lb) || real_isinf (&op1_ub))))
|
|
{
|
|
// If both lhs and op1 could be zeros or both could be infinities,
|
|
// we don't know anything about op2 except maybe for the sign
|
|
// and perhaps if it can be NAN or not.
|
|
REAL_VALUE_TYPE lb, ub;
|
|
int signbit_known = signbit_known_p (lhs_lb, lhs_ub, op1_lb, op1_ub);
|
|
zero_to_inf_range (lb, ub, signbit_known);
|
|
r.set (type, lb, ub);
|
|
}
|
|
return float_binary_op_range_finish (ret, r, type, wlhs, true);
|
|
}
|
|
private:
|
|
void rv_fold (frange &r, tree type,
|
|
const REAL_VALUE_TYPE &lh_lb,
|
|
const REAL_VALUE_TYPE &lh_ub,
|
|
const REAL_VALUE_TYPE &rh_lb,
|
|
const REAL_VALUE_TYPE &rh_ub,
|
|
relation_kind) const final override
|
|
{
|
|
// +-0.0 / +-0.0 or +-INF / +-INF is a known NAN.
|
|
if ((zero_p (lh_lb, lh_ub) && zero_p (rh_lb, rh_ub))
|
|
|| (singleton_inf_p (lh_lb, lh_ub) && singleton_inf_p (rh_lb, rh_ub)))
|
|
{
|
|
r.set_nan (type);
|
|
return;
|
|
}
|
|
|
|
REAL_VALUE_TYPE lb, ub;
|
|
bool maybe_nan = false;
|
|
// If +-0.0 is in both ranges, it is a maybe NAN.
|
|
if (contains_zero_p (lh_lb, lh_ub) && contains_zero_p (rh_lb, rh_ub))
|
|
maybe_nan = true;
|
|
// If +-INF is in both ranges, it is a maybe NAN.
|
|
else if ((real_isinf (&lh_lb) || real_isinf (&lh_ub))
|
|
&& (real_isinf (&rh_lb) || real_isinf (&rh_ub)))
|
|
maybe_nan = true;
|
|
|
|
int signbit_known = signbit_known_p (lh_lb, lh_ub, rh_lb, rh_ub);
|
|
|
|
// If dividend must be zero, the range is just +-0
|
|
// (including if the divisor is +-INF).
|
|
// If divisor must be +-INF, the range is just +-0
|
|
// (including if the dividend is zero).
|
|
if (zero_p (lh_lb, lh_ub) || singleton_inf_p (rh_lb, rh_ub))
|
|
{
|
|
zero_range (lb, ub, signbit_known);
|
|
r.set (type, lb, ub, nan_state (maybe_nan));
|
|
return;
|
|
}
|
|
|
|
// If divisor must be zero, the range is just +-INF
|
|
// (including if the dividend is +-INF).
|
|
// If dividend must be +-INF, the range is just +-INF
|
|
// (including if the dividend is zero).
|
|
if (zero_p (rh_lb, rh_ub) || singleton_inf_p (lh_lb, lh_ub))
|
|
{
|
|
inf_range (lb, ub, signbit_known);
|
|
r.set (type, lb, ub, nan_state (maybe_nan));
|
|
return;
|
|
}
|
|
|
|
// Otherwise if both operands may be zero, divisor could be
|
|
// nextafter(0.0, +-1.0) and dividend +-0.0
|
|
// in which case result is going to INF or vice versa and
|
|
// result +0.0. So, all we can say for that case is if the
|
|
// signs of divisor and dividend are always the same we have
|
|
// [+0.0, +INF], if they are always different we have
|
|
// [-INF, -0.0]. If they vary, VARYING.
|
|
// If both may be +-INF, divisor could be INF and dividend FLT_MAX,
|
|
// in which case result is going to INF or vice versa and
|
|
// result +0.0. So, all we can say for that case is if the
|
|
// signs of divisor and dividend are always the same we have
|
|
// [+0.0, +INF], if they are always different we have
|
|
// [-INF, -0.0]. If they vary, VARYING.
|
|
if (maybe_nan)
|
|
{
|
|
zero_to_inf_range (lb, ub, signbit_known);
|
|
r.set (type, lb, ub, nan_state (maybe_nan));
|
|
return;
|
|
}
|
|
|
|
REAL_VALUE_TYPE cp[8];
|
|
// Do a cross-division. At this point none of the divisions should
|
|
// produce a NAN.
|
|
frange_arithmetic (RDIV_EXPR, type, cp[0], lh_lb, rh_lb, dconstninf);
|
|
frange_arithmetic (RDIV_EXPR, type, cp[1], lh_lb, rh_ub, dconstninf);
|
|
frange_arithmetic (RDIV_EXPR, type, cp[2], lh_ub, rh_lb, dconstninf);
|
|
frange_arithmetic (RDIV_EXPR, type, cp[3], lh_ub, rh_ub, dconstninf);
|
|
frange_arithmetic (RDIV_EXPR, type, cp[4], lh_lb, rh_lb, dconstinf);
|
|
frange_arithmetic (RDIV_EXPR, type, cp[5], lh_lb, rh_ub, dconstinf);
|
|
frange_arithmetic (RDIV_EXPR, type, cp[6], lh_ub, rh_lb, dconstinf);
|
|
frange_arithmetic (RDIV_EXPR, type, cp[7], lh_ub, rh_ub, dconstinf);
|
|
|
|
find_range (lb, ub, cp);
|
|
|
|
// If divisor may be zero (but is not known to be only zero),
|
|
// and dividend can't be zero, the range can go up to -INF or +INF
|
|
// depending on the signs.
|
|
if (contains_zero_p (rh_lb, rh_ub))
|
|
{
|
|
if (signbit_known <= 0)
|
|
real_inf (&lb, true);
|
|
if (signbit_known >= 0)
|
|
real_inf (&ub, false);
|
|
}
|
|
|
|
gcc_checking_assert (!real_isnan (&lb));
|
|
gcc_checking_assert (!real_isnan (&ub));
|
|
r.set (type, lb, ub, nan_state (maybe_nan));
|
|
}
|
|
} fop_div;
|
|
|
|
bool
|
|
operator_cast::fold_range (frange &r, tree type, const frange &op1,
|
|
const frange &, relation_trio) const
|
|
{
|
|
REAL_VALUE_TYPE lb, ub;
|
|
enum machine_mode mode = TYPE_MODE (type);
|
|
bool mode_composite = MODE_COMPOSITE_P (mode);
|
|
|
|
if (empty_range_varying (r, type, op1, op1))
|
|
return true;
|
|
if (!MODE_HAS_NANS (mode) && op1.maybe_isnan ())
|
|
{
|
|
r.set_varying (type);
|
|
return true;
|
|
}
|
|
if (op1.known_isnan ())
|
|
{
|
|
r.set_nan (type);
|
|
return true;
|
|
}
|
|
|
|
const REAL_VALUE_TYPE &lh_lb = op1.lower_bound ();
|
|
const REAL_VALUE_TYPE &lh_ub = op1.upper_bound ();
|
|
real_convert (&lb, mode, &lh_lb);
|
|
real_convert (&ub, mode, &lh_ub);
|
|
|
|
if (flag_rounding_math)
|
|
{
|
|
if (real_less (&lh_lb, &lb))
|
|
{
|
|
if (mode_composite
|
|
&& (real_isdenormal (&lb, mode) || real_iszero (&lb)))
|
|
{
|
|
// IBM extended denormals only have DFmode precision.
|
|
REAL_VALUE_TYPE tmp, tmp2;
|
|
real_convert (&tmp2, DFmode, &lh_lb);
|
|
real_nextafter (&tmp, REAL_MODE_FORMAT (DFmode), &tmp2,
|
|
&dconstninf);
|
|
real_convert (&lb, mode, &tmp);
|
|
}
|
|
else
|
|
frange_nextafter (mode, lb, dconstninf);
|
|
}
|
|
if (real_less (&ub, &lh_ub))
|
|
{
|
|
if (mode_composite
|
|
&& (real_isdenormal (&ub, mode) || real_iszero (&ub)))
|
|
{
|
|
// IBM extended denormals only have DFmode precision.
|
|
REAL_VALUE_TYPE tmp, tmp2;
|
|
real_convert (&tmp2, DFmode, &lh_ub);
|
|
real_nextafter (&tmp, REAL_MODE_FORMAT (DFmode), &tmp2,
|
|
&dconstinf);
|
|
real_convert (&ub, mode, &tmp);
|
|
}
|
|
else
|
|
frange_nextafter (mode, ub, dconstinf);
|
|
}
|
|
}
|
|
|
|
r.set (type, lb, ub, op1.get_nan_state ());
|
|
|
|
if (flag_trapping_math
|
|
&& MODE_HAS_INFINITIES (TYPE_MODE (type))
|
|
&& r.known_isinf ()
|
|
&& !op1.known_isinf ())
|
|
{
|
|
REAL_VALUE_TYPE inf = r.lower_bound ();
|
|
if (real_isneg (&inf))
|
|
{
|
|
REAL_VALUE_TYPE min = real_min_representable (type);
|
|
r.set (type, inf, min);
|
|
}
|
|
else
|
|
{
|
|
REAL_VALUE_TYPE max = real_max_representable (type);
|
|
r.set (type, max, inf);
|
|
}
|
|
}
|
|
|
|
r.flush_denormals_to_zero ();
|
|
return true;
|
|
}
|
|
|
|
// Implement fold for a cast from float to another float.
|
|
bool
|
|
operator_cast::op1_range (frange &r, tree type, const frange &lhs,
|
|
const frange &op2, relation_trio) const
|
|
{
|
|
if (lhs.undefined_p ())
|
|
return false;
|
|
tree lhs_type = lhs.type ();
|
|
enum machine_mode mode = TYPE_MODE (type);
|
|
enum machine_mode lhs_mode = TYPE_MODE (lhs_type);
|
|
frange wlhs;
|
|
bool rm;
|
|
if (REAL_MODE_FORMAT (mode)->ieee_bits
|
|
&& REAL_MODE_FORMAT (lhs_mode)->ieee_bits
|
|
&& (REAL_MODE_FORMAT (lhs_mode)->ieee_bits
|
|
>= REAL_MODE_FORMAT (mode)->ieee_bits)
|
|
&& pow2p_hwi (REAL_MODE_FORMAT (mode)->ieee_bits))
|
|
{
|
|
/* If the cast is widening from IEEE exchange mode to
|
|
wider exchange mode or extended mode, no need to extend
|
|
the range on reverse operation. */
|
|
rm = false;
|
|
wlhs = lhs;
|
|
}
|
|
else
|
|
{
|
|
rm = true;
|
|
wlhs = float_widen_lhs_range (lhs_type, lhs);
|
|
}
|
|
auto save_flag_rounding_math = flag_rounding_math;
|
|
flag_rounding_math = rm;
|
|
bool ret = float_binary_op_range_finish (fold_range (r, type, wlhs, op2),
|
|
r, type, lhs);
|
|
flag_rounding_math = save_flag_rounding_math;
|
|
return ret;
|
|
}
|
|
|
|
// Implement fold for a cast from float to an int.
|
|
bool
|
|
operator_cast::fold_range (irange &r, tree type, const frange &op1,
|
|
const irange &, relation_trio) const
|
|
{
|
|
if (empty_range_varying (r, type, op1, op1))
|
|
return true;
|
|
if (op1.maybe_isnan () || op1.maybe_isinf ())
|
|
{
|
|
r.set_varying (type);
|
|
return true;
|
|
}
|
|
REAL_VALUE_TYPE lb, ub;
|
|
real_trunc (&lb, VOIDmode, &op1.lower_bound ());
|
|
real_trunc (&ub, VOIDmode, &op1.upper_bound ());
|
|
REAL_VALUE_TYPE l, u;
|
|
l = real_value_from_int_cst (NULL_TREE, TYPE_MIN_VALUE (type));
|
|
if (real_less (&lb, &l))
|
|
{
|
|
r.set_varying (type);
|
|
return true;
|
|
}
|
|
u = real_value_from_int_cst (NULL_TREE, TYPE_MAX_VALUE (type));
|
|
if (real_less (&u, &ub))
|
|
{
|
|
r.set_varying (type);
|
|
return true;
|
|
}
|
|
bool fail = false;
|
|
wide_int wlb = real_to_integer (&lb, &fail, TYPE_PRECISION (type));
|
|
wide_int wub = real_to_integer (&ub, &fail, TYPE_PRECISION (type));
|
|
if (fail)
|
|
{
|
|
r.set_varying (type);
|
|
return true;
|
|
}
|
|
r.set (type, wlb, wub);
|
|
return true;
|
|
}
|
|
|
|
// Implement op1_range for a cast from float to an int.
|
|
bool
|
|
operator_cast::op1_range (frange &r, tree type, const irange &lhs,
|
|
const frange &, relation_trio) const
|
|
{
|
|
if (lhs.undefined_p ())
|
|
return false;
|
|
REAL_VALUE_TYPE lb, lbo, ub, ubo;
|
|
wide_int lhs_lb = lhs.lower_bound ();
|
|
wide_int lhs_ub = lhs.upper_bound ();
|
|
tree lhs_type = lhs.type ();
|
|
enum machine_mode mode = TYPE_MODE (type);
|
|
real_from_integer (&lbo, VOIDmode, lhs_lb, TYPE_SIGN (lhs_type));
|
|
real_from_integer (&ubo, VOIDmode, lhs_ub, TYPE_SIGN (lhs_type));
|
|
real_convert (&lb, mode, &lbo);
|
|
real_convert (&ub, mode, &ubo);
|
|
if (real_identical (&lb, &lbo))
|
|
{
|
|
/* If low bound is exactly representable in type,
|
|
use nextafter (lb - 1., +inf). */
|
|
real_arithmetic (&lb, PLUS_EXPR, &lbo, &dconstm1);
|
|
real_convert (&lb, mode, &lb);
|
|
if (!real_identical (&lb, &lbo))
|
|
frange_nextafter (mode, lb, dconstinf);
|
|
if (real_identical (&lb, &lbo))
|
|
frange_nextafter (mode, lb, dconstninf);
|
|
}
|
|
else if (real_less (&lbo, &lb))
|
|
frange_nextafter (mode, lb, dconstninf);
|
|
if (real_identical (&ub, &ubo))
|
|
{
|
|
/* If upper bound is exactly representable in type,
|
|
use nextafter (ub + 1., -inf). */
|
|
real_arithmetic (&ub, PLUS_EXPR, &ubo, &dconst1);
|
|
real_convert (&ub, mode, &ub);
|
|
if (!real_identical (&ub, &ubo))
|
|
frange_nextafter (mode, ub, dconstninf);
|
|
if (real_identical (&ub, &ubo))
|
|
frange_nextafter (mode, ub, dconstinf);
|
|
}
|
|
else if (real_less (&ub, &ubo))
|
|
frange_nextafter (mode, ub, dconstinf);
|
|
r.set (type, lb, ub, nan_state (false));
|
|
return true;
|
|
}
|
|
|
|
// Implement fold for a cast from int to a float.
|
|
bool
|
|
operator_cast::fold_range (frange &r, tree type, const irange &op1,
|
|
const frange &, relation_trio) const
|
|
{
|
|
if (empty_range_varying (r, type, op1, op1))
|
|
return true;
|
|
REAL_VALUE_TYPE lb, ub;
|
|
wide_int op1_lb = op1.lower_bound ();
|
|
wide_int op1_ub = op1.upper_bound ();
|
|
tree op1_type = op1.type ();
|
|
enum machine_mode mode = flag_rounding_math ? VOIDmode : TYPE_MODE (type);
|
|
real_from_integer (&lb, mode, op1_lb, TYPE_SIGN (op1_type));
|
|
real_from_integer (&ub, mode, op1_ub, TYPE_SIGN (op1_type));
|
|
if (flag_rounding_math)
|
|
{
|
|
REAL_VALUE_TYPE lbo = lb, ubo = ub;
|
|
mode = TYPE_MODE (type);
|
|
real_convert (&lb, mode, &lb);
|
|
real_convert (&ub, mode, &ub);
|
|
if (real_less (&lbo, &lb))
|
|
frange_nextafter (mode, lb, dconstninf);
|
|
if (real_less (&ub, &ubo))
|
|
frange_nextafter (mode, ub, dconstinf);
|
|
}
|
|
r.set (type, lb, ub, nan_state (false));
|
|
frange_drop_infs (r, type);
|
|
if (r.undefined_p ())
|
|
r.set_varying (type);
|
|
return true;
|
|
}
|
|
|
|
// Implement op1_range for a cast from int to a float.
|
|
bool
|
|
operator_cast::op1_range (irange &r, tree type, const frange &lhs,
|
|
const irange &, relation_trio) const
|
|
{
|
|
if (lhs.undefined_p ())
|
|
return false;
|
|
if (lhs.known_isnan ())
|
|
{
|
|
r.set_varying (type);
|
|
return true;
|
|
}
|
|
REAL_VALUE_TYPE lb = lhs.lower_bound ();
|
|
REAL_VALUE_TYPE ub = lhs.upper_bound ();
|
|
enum machine_mode mode = TYPE_MODE (lhs.type ());
|
|
frange_nextafter (mode, lb, dconstninf);
|
|
frange_nextafter (mode, ub, dconstinf);
|
|
if (flag_rounding_math)
|
|
{
|
|
real_floor (&lb, mode, &lb);
|
|
real_ceil (&ub, mode, &ub);
|
|
}
|
|
else
|
|
{
|
|
real_trunc (&lb, mode, &lb);
|
|
real_trunc (&ub, mode, &ub);
|
|
}
|
|
REAL_VALUE_TYPE l, u;
|
|
wide_int wlb, wub;
|
|
l = real_value_from_int_cst (NULL_TREE, TYPE_MIN_VALUE (type));
|
|
if (real_less (&lb, &l))
|
|
wlb = wi::min_value (TYPE_PRECISION (type), TYPE_SIGN (type));
|
|
else
|
|
{
|
|
bool fail = false;
|
|
wlb = real_to_integer (&lb, &fail, TYPE_PRECISION (type));
|
|
if (fail)
|
|
wlb = wi::min_value (TYPE_PRECISION (type), TYPE_SIGN (type));
|
|
}
|
|
u = real_value_from_int_cst (NULL_TREE, TYPE_MAX_VALUE (type));
|
|
if (real_less (&u, &ub))
|
|
wub = wi::max_value (TYPE_PRECISION (type), TYPE_SIGN (type));
|
|
else
|
|
{
|
|
bool fail = false;
|
|
wub = real_to_integer (&ub, &fail, TYPE_PRECISION (type));
|
|
if (fail)
|
|
wub = wi::max_value (TYPE_PRECISION (type), TYPE_SIGN (type));
|
|
}
|
|
r.set (type, wlb, wub);
|
|
return true;
|
|
}
|
|
|
|
// Initialize any float operators to the primary table
|
|
|
|
void
|
|
range_op_table::initialize_float_ops ()
|
|
{
|
|
set (UNLE_EXPR, fop_unordered_le);
|
|
set (UNLT_EXPR, fop_unordered_lt);
|
|
set (UNGE_EXPR, fop_unordered_ge);
|
|
set (UNGT_EXPR, fop_unordered_gt);
|
|
set (UNEQ_EXPR, fop_unordered_equal);
|
|
set (ORDERED_EXPR, fop_ordered);
|
|
set (UNORDERED_EXPR, fop_unordered);
|
|
set (LTGT_EXPR, fop_ltgt);
|
|
set (RDIV_EXPR, fop_div);
|
|
}
|
|
|
|
#if CHECKING_P
|
|
#include "selftest.h"
|
|
|
|
namespace selftest
|
|
{
|
|
|
|
// Build an frange from string endpoints.
|
|
|
|
static inline frange
|
|
frange_float (const char *lb, const char *ub, tree type = float_type_node)
|
|
{
|
|
REAL_VALUE_TYPE min, max;
|
|
gcc_assert (real_from_string (&min, lb) == 0);
|
|
gcc_assert (real_from_string (&max, ub) == 0);
|
|
return frange (type, min, max);
|
|
}
|
|
|
|
void
|
|
range_op_float_tests ()
|
|
{
|
|
frange r, r0, r1;
|
|
frange trange (float_type_node);
|
|
|
|
// negate([-5, +10]) => [-10, 5]
|
|
r0 = frange_float ("-5", "10");
|
|
range_op_handler (NEGATE_EXPR).fold_range (r, float_type_node, r0, trange);
|
|
ASSERT_EQ (r, frange_float ("-10", "5"));
|
|
|
|
// negate([0, 1] -NAN) => [-1, -0] +NAN
|
|
r0 = frange_float ("0", "1");
|
|
r0.update_nan (true);
|
|
range_op_handler (NEGATE_EXPR).fold_range (r, float_type_node, r0, trange);
|
|
r1 = frange_float ("-1", "-0");
|
|
r1.update_nan (false);
|
|
ASSERT_EQ (r, r1);
|
|
|
|
// [-INF,+INF] + [-INF,+INF] could be a NAN.
|
|
range_op_handler plus (PLUS_EXPR);
|
|
r0.set_varying (float_type_node);
|
|
r1.set_varying (float_type_node);
|
|
r0.clear_nan ();
|
|
r1.clear_nan ();
|
|
plus.fold_range (r, float_type_node, r0, r1);
|
|
if (HONOR_NANS (float_type_node))
|
|
ASSERT_TRUE (r.maybe_isnan ());
|
|
}
|
|
|
|
} // namespace selftest
|
|
|
|
#endif // CHECKING_P
|