/* Subsets of a finite interval over Z.
Copyright (C) 2024-2025 Free Software Foundation, Inc.
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
. */
/* A class that represents the union of finitely many intervals.
The domain over which the intervals are defined is a finite integer
interval [m_min, m_max], usually the range of some [u]intN_t.
Supported operations are:
- Complement w.r.t. the domain (invert)
- Union (union_)
- Intersection (intersect)
- Difference / Setminus (minus).
Ranges is closed under all operations: The result of all operations
is a Ranges over the same domain. (As opposed to value-range.h which
may ICE for some operations, see below).
The representation is unique in the sense that when we have two
Ranges A and B, then
1) A == B <==> A.size == B.size && Ai == Bi for all i.
The representation is normalized:
2) Ai != {} ;; There are no empty intervals.
3) Ai.hi < A{i+1}.lo ;; The Ai's are in increasing order and separated
;; by at least one value (non-adjacent).
The sub-intervals Ai are maintained as a std::vector.
The computation of union and intersection scales like A.size * B.size
i.e. Ranges is only eligible for GCC when size() has a fixed upper
bound independent of the program being compiled (or there are other
means to guarantee that the complexity is linearistic).
In the context of AVR, we have size() <= 3.
The reason why we don't use value-range.h's irange or int_range is that
these use the integers Z as their domain, which makes computations like
invert() quite nasty as they may ICE for common cases. Doing all
these special cases (like one sub-interval touches the domain bounds)
makes using value-range.h more laborious (and instable) than using our
own mini Ranger. */
struct Ranges
{
// This is good enough as it covers (un)signed SImode.
using T = HOST_WIDE_INT;
typedef T scalar_type;
// Non-empty ranges. Empty sets are only used transiently;
// Ranges.ranges[] doesn't use them.
struct SubRange
{
// Lower and upper bound, inclusively.
T lo, hi;
SubRange intersect (const SubRange &r) const
{
if (lo >= r.lo && hi <= r.hi)
return *this;
else if (r.lo >= lo && r.hi <= hi)
return r;
else if (lo > r.hi || hi < r.lo)
return SubRange { 1, 0 };
else
return SubRange { std::max (lo, r.lo), std::min (hi, r.hi) };
}
T cardinality () const
{
return std::max (0, hi - lo + 1);
}
};
// Finitely many intervals over [m_min, m_max] that are normalized:
// No empty sets, increasing order, separated by at least one value.
T m_min, m_max;
std::vector ranges;
// Not used anywhere in Ranges; can be used elsewhere.
// May be clobbered by set operations.
int tag = -1;
enum initial_range { EMPTY, ALL };
Ranges (T mi, T ma, initial_range ir)
: m_min (mi), m_max (ma)
{
if (ir == ALL)
push (mi, ma);
}
// Domain is the range of some [u]intN_t.
static Ranges NBitsRanges (int n_bits, bool unsigned_p, initial_range ir)
{
T mask = ((T) 1 << n_bits) - 1;
gcc_assert (mask > 0);
T ma = mask >> ! unsigned_p;
return Ranges (unsigned_p ? 0 : -ma - 1, ma, ir);
}
static void sort2 (Ranges &a, Ranges &b)
{
if (a.size () && b.size ())
if (a.ranges[0].lo > b.ranges[0].lo)
std::swap (a, b);
}
void print (FILE *file) const
{
if (file)
{
fprintf (file, " .tag%d=#%d={", tag, size ());
for (const auto &r : ranges)
fprintf (file, "[ %ld, %ld ]", (long) r.lo, (long) r.hi);
fprintf (file, "}\n");
}
}
// The number of sub-intervals in .ranges.
int size () const
{
return (int) ranges.size ();
}
// Append [LO, HI] & [m_min, m_max] to .ranges provided the
// former is non-empty.
void push (T lo, T hi)
{
lo = std::max (lo, m_min);
hi = std::min (hi, m_max);
if (lo <= hi)
ranges.push_back (SubRange { lo, hi });
}
// Append R to .ranges provided the former is non-empty.
void push (const SubRange &r)
{
push (r.lo, r.hi);
}
// Cardinality of the n-th interval.
T cardinality (int n) const
{
return n < size () ? ranges[n].cardinality () : 0;
}
// Check that *this is normalized: .ranges are non-empty, non-overlapping,
// non-adjacent and increasing.
bool check () const
{
bool bad = size () && (ranges[0].lo < m_min
|| ranges[size () - 1].hi > m_max);
for (int n = 0; n < size (); ++n)
{
bad |= ranges[n].lo > ranges[n].hi;
bad |= n > 0 && ranges[n - 1].hi >= ranges[n].lo;
}
if (bad)
print (dump_file);
return ! bad;
}
// Intersect A and B according to (U Ai) & (U Bj) = U (Ai & Bj)
// This has quadratic complexity, but also the nice property that
// when A and B are normalized, then the result is too.
void intersect (const Ranges &r)
{
gcc_assert (m_min == r.m_min && m_max == r.m_max);
if (this == &r)
return;
std::vector rs;
std::swap (rs, ranges);
for (const auto &a : rs)
for (const auto &b : r.ranges)
push (a.intersect (b));
}
// Complement w.r.t. the domain [m_min, m_max].
void invert ()
{
std::vector rs;
std::swap (rs, ranges);
if (rs.size () == 0)
push (m_min, m_max);
else
{
push (m_min, rs[0].lo - 1);
for (size_t n = 1; n < rs.size (); ++n)
push (rs[n - 1].hi + 1, rs[n].lo - 1);
push (rs[rs.size () - 1].hi + 1, m_max);
}
}
// Set-minus.
void minus (const Ranges &r)
{
gcc_assert (m_min == r.m_min && m_max == r.m_max);
Ranges sub = r;
sub.invert ();
intersect (sub);
}
// Union of sets. Not needed in avr.cc but added for completeness.
// DeMorgan this for simplicity.
void union_ (const Ranges &r)
{
gcc_assert (m_min == r.m_min && m_max == r.m_max);
if (this != &r)
{
invert ();
minus (r);
invert ();
}
}
// Get the truth Ranges for x val. For example,
// LT 3 will return [m_min, 2].
Ranges truth (rtx_code cmp, T val, bool strict = true)
{
if (strict)
{
if (avr_strict_signed_p (cmp))
gcc_assert (m_min == -m_max - 1);
else if (avr_strict_unsigned_p (cmp))
gcc_assert (m_min == 0);
gcc_assert (IN_RANGE (val, m_min, m_max));
}
bool rev = cmp == NE || cmp == LTU || cmp == LT || cmp == GTU || cmp == GT;
if (rev)
cmp = reverse_condition (cmp);
T lo = m_min;
T hi = m_max;
if (cmp == EQ)
lo = hi = val;
else if (cmp == LEU || cmp == LE)
hi = val;
else if (cmp == GEU || cmp == GE)
lo = val;
else
gcc_unreachable ();
Ranges rs (m_min, m_max, Ranges::EMPTY);
rs.push (lo, hi);
if (rev)
rs.invert ();
return rs;
}
}; // struct Ranges