https://github.com/shader-slang/slang
Raw File
Tip revision: 5902acdabc4445a65741a7a6a3a95f223e301059 authored by Yong He on 23 January 2024, 07:19:40 UTC
[LSP] Fetch configs directly from didConfigurationChanged message. (#3478)
Tip revision: 5902acd
slang-capability.cpp
// slang-capability.cpp
#include "slang-capability.h"

#include "../core/slang-dictionary.h"

// This file implements the core of the "capability" system.

namespace Slang
{

//
// CapabilityAtom
//

// We are going to divide capability atoms into a few categories.
//
enum class CapabilityNameFlavor : int32_t
{
    // A concrete capability atom is something that a target
    // can directly support, where the presence of the feature
    // directly provides functionality. A specific OpenGL
    // or Vulkan extension would be an example of a concrete
    // capability.
    //
    Concrete,

    // An abstract capability represents a class of feature
    // where multiple different implementations might be possible.
    // For example, "ray tracing" might be an abstract feature
    // that a function can require, but a specific target will
    // only be able to provide that abstract feature via some
    // specific concrete feature (e.g., `GL_EXT_ray_tracing`).
    Abstract,

    // An alias capability atom is one that is exactly equivalent
    // to the things it inherits from.
    //
    // For example, a `ps_5_1` capability would just be an
    // alias for the combination of the `fragment` capability
    // and the `sm_5_1` capability.
    //
    Alias,
};

// The macros in the `slang-capability-defs.h` file will be used
// to fill out a `static const` array of information about each
// capability atom.
//
struct CapabilityAtomInfo
{
    /// The API-/language-exposed name of the capability.
    char const* name;

    /// Flavor of atom: concrete, abstract, or alias
    CapabilityNameFlavor        flavor;

    /// If the atom is a direct descendent of an abstract base, keep that for reference here.
    CapabilityName abstractBase;

    /// Ranking to use when deciding if this atom is a "better" one to select.
    uint32_t                    rank;

    /// Canonical representation in the form of disjunction-of-conjunction of atoms.
    ArrayView<ArrayView<CapabilityName>> canonicalRepresentation;
};

#include "slang-generated-capability-defs-impl.h"

static CapabilityAtom asAtom(CapabilityName name)
{
    SLANG_ASSERT((CapabilityAtom)name < CapabilityAtom::Count);
    return (CapabilityAtom)name;
}

/// Get the extended information structure for the given capability `atom`
static CapabilityAtomInfo const& _getInfo(CapabilityName atom)
{
    SLANG_ASSERT(Int(atom) < Int(CapabilityName::Count));
    return kCapabilityNameInfos[Int(atom)];
}
static CapabilityAtomInfo const& _getInfo(CapabilityAtom atom)
{
    SLANG_ASSERT(Int(atom) < Int(CapabilityAtom::Count));
    return kCapabilityNameInfos[Int(atom)];
}

void getCapabilityNames(List<UnownedStringSlice>& ioNames)
{
    ioNames.reserve(Count(CapabilityName::Count));
    for (Index i = 0; i < Count(CapabilityName::Count); ++i)
    {
        if (_getInfo(CapabilityName(i)).flavor != CapabilityNameFlavor::Abstract)
        {
            ioNames.add(UnownedStringSlice(_getInfo(CapabilityName(i)).name));
        }
    }
}

bool lookupCapabilityName(const UnownedStringSlice& str, CapabilityName& value);

CapabilityName findCapabilityName(UnownedStringSlice const& name)
{
    CapabilityName result;
    if (!lookupCapabilityName(name, result))
        return CapabilityName::Invalid;
    return result;
}

bool isCapabilityDerivedFrom(CapabilityAtom atom, CapabilityAtom base)
{
    if (atom == base)
    {
        return true;
    }

    const auto& info = kCapabilityNameInfos[Index(atom)];
    for (auto cur : info.canonicalRepresentation)
    {
        for (auto cbase : cur)
            if (asAtom(cbase) == base)
                return true;
    }

    return false;
}

//
// CapabilityConjunctionSet
//

// The current design choice in `CapabilityConjunctionSet` is that it stores
// an expanded, deduplicated, and sorted list of the capability
// atoms in the set. "Expanded" here means that it includes the
// transitive closure of the inheritance graph of those atoms.
//
// This choice is intended to make certain operations on
// capability sets more efficient, since use things like
// binary searches to efficiently detect whether an atom
// is present in a set.

CapabilityConjunctionSet::CapabilityConjunctionSet()
{}

CapabilityConjunctionSet::CapabilityConjunctionSet(Int atomCount, CapabilityAtom const* atoms)
{
    _init(atomCount, atoms);
}

CapabilityConjunctionSet::CapabilityConjunctionSet(CapabilityAtom atom)
{
    _init(1, &atom);
}

CapabilityConjunctionSet::CapabilityConjunctionSet(List<CapabilityAtom> const& atoms)
{
    _init(atoms.getCount(), atoms.getBuffer());
}

CapabilityConjunctionSet CapabilityConjunctionSet::makeEmpty()
{
    return CapabilityConjunctionSet();
}

CapabilityConjunctionSet CapabilityConjunctionSet::makeInvalid()
{
    // An invalid capability set will always be a singleton
    // set of the `Invalid` atom, and we will construct
    // the set directly rather than use the more expensive
    // logic in `_init()`.
    //
    CapabilityConjunctionSet result;
    result.m_expandedAtoms.add(CapabilityAtom::Invalid);
    return result;
}

void CapabilityConjunctionSet::_init(Int atomCount, CapabilityAtom const* atoms)
{
    // We will use an explicit hash set to deduplicate input atoms.
    //
    HashSet<CapabilityAtom> expandedAtomsSet;
    for(Int i = 0; i < atomCount; ++i)
    {
        if (expandedAtomsSet.add(atoms[i]))
        {
            auto& info = _getInfo(atoms[i]);

            // Add the base items that this atom implies.
            if (info.canonicalRepresentation.getCount() == 1)
            {
                // The atom must have only one conjunction.
                SLANG_ASSERT(info.canonicalRepresentation.getCount() == 1);

                for (auto base : info.canonicalRepresentation[0])
                {
                    expandedAtomsSet.add(asAtom(base));
                }
            }
        }
    }

    // We can then translate the set of atoms into a list,
    // and then sort that list to produce the data that
    // we use in all our other queries.
    //
    for(auto atom : expandedAtomsSet)
    {
        m_expandedAtoms.add(atom);
    }
    m_expandedAtoms.sort();
}

void CapabilityConjunctionSet::calcCompactedAtoms(List<CapabilityAtom>& outAtoms) const
{
    // A "compacted" list of atoms is one that starts with
    // the "expanded" list and removes any atoms that are
    // implied by another atom already in the list.
    //
    // If the expanded list contains atom A, and A inherits
    // from B, then we know that the expanded list also contains B,
    // but the compacted list should not.
    //
    // We can thus look through the list of atoms A and for
    // each base B of A, add it to a set of "redundant" atoms
    // that need not appear in the compacted list.
    //
    HashSet<CapabilityAtom> redundantAtomsSet;
    for( auto atom : m_expandedAtoms )
    {
        auto& atomInfo = _getInfo(atom);
        if (atomInfo.canonicalRepresentation.getCount() != 1)
        {
            // If the atom is not a single conjunction, skip.
            continue;
        }
        for(auto baseAtom : atomInfo.canonicalRepresentation[0])
        {
            // Note: don't add atom itself into redundant set.
            if(asAtom(baseAtom) == atom)
                continue;

            redundantAtomsSet.add(asAtom(baseAtom));
        }
    }

    // Once we are done figuring out which atoms are redundant,
    // we can iterate over the expanded list and add all the
    // non-redundant ones to the compacted output list.
    //
    outAtoms.clear();
    for( auto atom : m_expandedAtoms )
    {
        if(!redundantAtomsSet.contains(atom))
        {
            outAtoms.add(atom);
        }
    }
}

bool CapabilityConjunctionSet::isEmpty() const
{
    // Checking if a capability set is empty is trivial in any representation;
    // all we need to know is if it has zero atoms in its definition.
    //
    return m_expandedAtoms.getCount() == 0;
}

bool CapabilityConjunctionSet::isInvalid() const
{
    // We will assume here that there is only one canonical representation of
    // an invalid capability set, which is a singleton set of the `Invalid`
    // atom.
    //
    // TODO: We should ensure that any algorithms that make new capability
    // sets by combining others properly ensure that they return the
    // canonical invalid set rather than any other set that happens to be
    // invalid (e.g., a set {A,B} would be invalid if A and B are incompatible,
    // but it would not be in the canonical form this subroutine checks).
    //
    if(m_expandedAtoms.getCount() != 1) return false;
    return m_expandedAtoms[0] == CapabilityAtom::Invalid;
}

bool CapabilityConjunctionSet::isIncompatibleWith(CapabilityAtom that) const
{
    // Checking for incompatibility is complicated, and it is best
    // to only implement it for full (expanded) sets.
    //
    return isIncompatibleWith(CapabilityConjunctionSet(that));
}

static UIntSet _calcConflictMask(CapabilityAtom atom)
{
    UIntSet mask;
    auto abstractBase = _getInfo(atom).abstractBase;
    if (abstractBase != CapabilityName::Invalid)
    {
        mask.add((UInt)abstractBase);
    }
    return mask;
}

static UIntSet _calcConflictMask(const CapabilityConjunctionSet& set)
{
    // Given a capbility set, we want to compute the mask representing
    // all groups of features for which it holds a potentially-conflicting atom.
    //
    UIntSet mask;
    for (auto atom : set.getExpandedAtoms())
    {
        auto abstractBase = _getInfo(atom).abstractBase;
        if (abstractBase != CapabilityName::Invalid)
        {
            mask.add((UInt)abstractBase);
        }
    }
    return mask;
}

bool CapabilityConjunctionSet::isIncompatibleWith(CapabilityConjunctionSet const& that) const
{
    // The `this` and `that` sets are incompatible if there exists
    // an atom A in `this` and an atom `B` in `that` such that
    // A and B are not equal, but the two have overlapping "conflict group."
    //
    // Equivalently, we can say that the two are in conflict if
    //
    // * One of the two sets contains an atom A with conflict mask M
    // * The other set contains at least one atom that conflicts with M
    // * The other set does not contain A
    //
    // Our approach here is all about minimizing the number of
    // iterations we take over lists of atoms, and trying to
    // avoid anything super-linear.

    // We start by identifying the OR of the conflict masks for
    // all features in `this` and `that`.
    //
    UIntSet thisMask = _calcConflictMask(*this);
    UIntSet thatMask = _calcConflictMask(that);

    // Note: there is a possible early-exit opportunity here if
    // `thisMask` and `thatMask` have no overlap: there could
    // be no conflicts in that case.

    // Next we will iterate over the two sets in tandem (O(N) time
    // in the size of the larger set), and identify any elements
    // that are present in one and not the other.
    //
    Index thisCount = this->m_expandedAtoms.getCount();
    Index thatCount = that.m_expandedAtoms.getCount();
    Index thisIndex = 0;
    Index thatIndex = 0;
    for(;;)
    {
        if(thisIndex == thisCount) break;
        if(thatIndex == thatCount) break;

        auto thisAtom = this->m_expandedAtoms[thisIndex];
        auto thatAtom = that.m_expandedAtoms[thatIndex];

        if(thisAtom == thatAtom)
        {
            thisIndex++;
            thatIndex++;
            continue;
        }

        if( thisAtom < thatAtom )
        {
            // `thisAtom` is present in `this` but not `that.
            //
            // If `thisAtom` has a conflict mask that overlaps
            // with `thatMask`, then we have a conflict: the
            // other set doesn't include `thisAtom`, but *does*
            // include something with an overlapping mask
            // (we don't know what at this point in the code).
            //
            auto thisConflictMask = Slang::_calcConflictMask(thisAtom);
            if(UIntSet::hasIntersection(thisConflictMask, thatMask))
                return true;
            thisIndex++;
        }
        else
        {
            SLANG_ASSERT(thisAtom > thatAtom);

            // `thatAtom` is present in `that` but not `this.
            //
            // The logic here is the mirror image of the case above.
            //
            auto thatConflictMask = Slang::_calcConflictMask(thatAtom);
            if(UIntSet::hasIntersection(thatConflictMask, thisMask))
                return true;
            thatIndex++;
        }
    }

    return false;
}

bool CapabilityConjunctionSet::implies(CapabilityConjunctionSet const& that) const
{
    // One capability set implies another if it is a super-set
    // of the other one. Think of it this way: if your target
    // supports features {X, Y, Z}, then that implies it also
    // supports features {X,Z}.
    //
    // Because both `this` and `that` have expanded lists
    // of all the capability atoms they imply *and* those
    // lists are sorted, we can simply walk through the
    // lists in tandem and see if there are any entries
    // in `that` which are not present in `this.

    Index thisCount = this->m_expandedAtoms.getCount();
    Index thatCount = that.m_expandedAtoms.getCount();

    // We cannot possibly have `this` contain all the atoms
    // in `that` if the latter is has more atoms.
    //
    if(thatCount > thisCount)
        return false;

    // Note: the following iteration is O(N) in the size
    // of the larger of the two sets, which is probably
    // needlessly inefficient. We might expect that `that`
    // will often be a much smaller set, and we'd like to
    // scale in its size rather than the size of `this`.
    //
    // A more advanced algorithm here would be to do
    // something recursive:
    //
    // * If `that` is  singleton set, then we can find
    //   whether `this` contains it via binary search.
    //
    // * Otherwise, we can split `that` into two
    //   equally-sized subsets. By taking a "pivot" value
    //   from where that split took place we can then
    //   use a binary search to partition `this` into
    //   two subsets and recurse on each side of that
    //   partition.
    //
    // In practice, the size of the sets we are dealing
    // with right now doesn't justify such a "clever" algorithm.

    Index thisIndex = 0;
    Index thatIndex = 0;
    for(;;)
    {
        if(thisIndex == thisCount) break;
        if(thatIndex == thatCount) break;

        auto thisAtom = this->m_expandedAtoms[thisIndex];
        auto thatAtom = that.m_expandedAtoms[thatIndex];

        if( thisAtom == thatAtom )
        {
            // We have an atom that both sets contain;
            // we should skip past it and keep looking.
            //
            thisIndex++;
            thatIndex++;
            continue;
        }

        if( thisAtom < thatAtom )
        {
            // We have an atom that `this` contains,
            // but `that` doesn't; that is consistent
            // with `this` being a super-set, so we
            // just skip the item and keep searching.
            //
            thisIndex++;
        }
        else
        {
            SLANG_ASSERT(thisAtom > thatAtom);

            // We have an atom in `that` which isn't
            // also in `this`, so we know it cannot
            // be a subset.
            //
            return false;
        }
    }
    return true;
}

    /// Helper functor for binary search on lists of `CapabilityAtom`
struct CapabilityAtomComparator
{
    int operator()(CapabilityAtom left, CapabilityAtom right)
    {
        return int(Int(left) - Int(right));
    }
};

bool CapabilityConjunctionSet::implies(CapabilityAtom atom) const
{
    // Every non-alias atom that `this` implies should
    // be presented in the `m_expandedAtoms` list.
    //
    // Because the list is sorted, we can find out whether
    // it contains `atom` with a binary search.
    //
    Index result = m_expandedAtoms.binarySearch(atom, CapabilityAtomComparator());
    return result >= 0;
}

Int CapabilityConjunctionSet::countIntersectionWith(CapabilityConjunctionSet const& that) const
{
    // The goal of this subroutine is to count the number of
    // elements in the intersection of `this` and `that`,
    // without explicitly forming that intersection.
    //
    // Our approach here will be to iterate over the two
    // sets in tandem (O(N) in the size of the larger set)
    // and check for elements that both contain.
    //
    // TODO: There should be an asymptotically faster
    // recursive algorithm here.

    Int intersectionCount = 0;

    Index thisCount = this->m_expandedAtoms.getCount();
    Index thatCount = that.m_expandedAtoms.getCount();
    Index thisIndex = 0;
    Index thatIndex = 0;
    for(;;)
    {
        if(thisIndex == thisCount) break;
        if(thatIndex == thatCount) break;

        auto thisAtom = this->m_expandedAtoms[thisIndex];
        auto thatAtom = that.m_expandedAtoms[thatIndex];

        if( thisAtom == thatAtom )
        {
            // An item both contain.

            intersectionCount++;
            thisIndex++;
            thatIndex++;
            continue;
        }

        if( thisAtom < thatAtom )
        {
            // An item in `this` but not `that`.

            thisIndex++;
        }
        else
        {
            SLANG_ASSERT(thisAtom > thatAtom);

            // An item in `that` but not `this`.

            thatIndex++;
        }
    }
    return intersectionCount;
}

bool CapabilityConjunctionSet::isBetterForTarget(
    CapabilityConjunctionSet const& existingCaps,
    CapabilityConjunctionSet const& targetCaps) const
{
    auto& candidateCaps = *this;

    // The task here is to determine if `candidateCaps` should
    // be considered "better" than `existingCaps` in the context
    // of compilation for a target with the given `targetCaps`.
    //
    // In an ideal world, this computation could be quite simple:
    //
    // * If either `candidateCaps` or `existingCaps` is not implied by
    //   `targetCaps` (that is, they include requirements that aren't
    //   provided by the target), then the other is automatically "better."
    //
    // * Otherwise, one set is "better" than the other if it is a
    //   super-set (which is what `implies()` tests).
    //
    // There are two main reasons we can't use that simple logic:
    //
    // 1. Currently a user of Slang can compile for a target but
    //    not actually spell out its capabilities fully or correctly.
    //    They might compile for `sm_5_0` but use ray tracing features
    //    that require `sm_6_2` and expect the compiler to figure out
    //    what they "obviously" meant. Thus we cannot assume that
    //    `targetCaps` can be used to rule out candidates fully.
    //
    // 2. Sometimes there are multiple ways for a target to provide
    //    the same feature (e.g., multiple extensions) and because of (1)
    //    we cannot always rely on the `targetCaps` to tell us which to
    //    use. Thus we cannot rely on pure subset/`implies()` to define
    //    better-ness, and need some way to break ties.
    //
    // The following logic is a bunch of "do what I mean" nonsense that
    // tries to capture a reasonable intuition of what "better"-ness
    // should mean with these caveats.

    // First, if either candidate is fundamentally incompatible
    // with the target, we shouldn't favor it.
    //
    if(candidateCaps.isIncompatibleWith(targetCaps)) return false;
    if(existingCaps.isIncompatibleWith(targetCaps)) return true;

    // Next, we want to compare the candidates to the `targetCaps`
    // to figure out whether one is obviously "more specialized" for
    // the target.
    //
    // We measure the degree to which a candidate is specialized for
    // the target as the size of its set intersection with `targetCaps`.
    //
    // TODO: If both `candidateCaps` and `existingCaps` are implied
    // by `targetCaps`, then this amounts to just measuring the
    // size of each set. We probably want this size-based check to
    // come later in the overall process.
    //
    // TODO: A better model here might be to actually compute the actual
    // intersected sets, and then check if one is a super-set of the other.
    //
    auto candidateIntersectionSize = targetCaps.countIntersectionWith(candidateCaps);
    auto existingIntersectionSize = targetCaps.countIntersectionWith(existingCaps);
    if(candidateIntersectionSize != existingIntersectionSize)
        return candidateIntersectionSize > existingIntersectionSize;

    // Next we want to consider that if one of the two candidates
    // is actually available on the target (meaning that it is
    // implied by `targetCaps`) then we probably want to pick that one
    // (since we can use that candidate on the chosen target without
    // enabling any additional features the user didn't ask for).
    //
    // TODO: This step currently needs to come after the preceeding
    // one because otherwise we risk selecting a `__target_intrinsic`
    // decoration with *no* requirements (which are currently being
    // added implicitly in many places) over any one with explicit
    // requirements (since every target implies the empty set of
    // requirements).
    //
    // In many ways the counting-based logic above amounts to a quick
    // fix to prefer a non-empty set of requirements over an empty one,
    // so long as something in that non-empty set overlaps with the target.
    //
    // TODO: The best fix is probably to figure out how "catch-all"
    // intrinsic function definitions should be encoded; we clearly
    // want them to be used only as a fallback when no target-specific
    // variants are present.
    //
    bool candidateIsAvailable = targetCaps.implies(candidateCaps);
    bool existingIsAvailable = targetCaps.implies(existingCaps);
    if(candidateIsAvailable != existingIsAvailable)
        return candidateIsAvailable;

    // All preceding factors being equal, we prefer
    // a candidate that is strictly more specialized than the other.
    //
    // We want to avoid choosing the candidate that uses
    // optional features if they aren't necessary.
    // For example, the set {glsl, optionalFeature} should not be preferred
    // over the set {glsl}, if optionalFeature isn't requested explictly.
    //
    // The solution here is that we want to partition
    // `candidateCaps` and `existingCaps` into two parts: their
    // intersection with `targetCaps` and their difference with it.
    //
    // For the intersection part of things, we'd want to favor a
    // definition that is more specialized, while for the difference
    // part we'd actually wnat to favor a definition that is less
    // specialized.
    //
    CapabilityConjunctionSet candidateCapsIntersection;
    CapabilityConjunctionSet candidateCapsDifference;
    for (auto atom : candidateCaps.m_expandedAtoms)
    {
        if (targetCaps.implies(atom))
            candidateCapsIntersection.m_expandedAtoms.add(atom);
        else
            candidateCapsDifference.m_expandedAtoms.add(atom);
    }
    CapabilityConjunctionSet existingCapsIntersection;
    CapabilityConjunctionSet existingCapsDifference;
    for (auto atom : existingCaps.m_expandedAtoms)
    {
        if (targetCaps.implies(atom))
            existingCapsIntersection.m_expandedAtoms.add(atom);
        else
            existingCapsDifference.m_expandedAtoms.add(atom);
    }
    auto scoreCandidate = candidateCapsIntersection.m_expandedAtoms.getCount() - candidateCapsDifference.m_expandedAtoms.getCount();
    auto scoreExisting = existingCapsIntersection.m_expandedAtoms.getCount() - existingCapsDifference.m_expandedAtoms.getCount();
    if (scoreCandidate != scoreExisting)
        return scoreCandidate > scoreExisting;

    // At this point we have the problem that neither candidate
    // appears to be "obviously" better for the target, but we
    // want some way to disambiguate them.
    //
    // What we want to do now is scan through what makes each candidate
    // different from the other, and see if anything in either case
    // has a ranking that should make it be preferred.
    //
    auto candidateScore = candidateCapsDifference._calcDifferenceScoreWith(existingCapsDifference);
    auto existingScore = existingCapsDifference._calcDifferenceScoreWith(candidateCapsDifference);
    if(candidateScore != existingScore)
        return candidateScore > existingScore;

    return false;
}

uint32_t CapabilityConjunctionSet::_calcDifferenceScoreWith(CapabilityConjunctionSet const& that) const
{
    uint32_t score = 0;

    // Our approach here will be to scan through `this` and `that`
    // to identify atoms that are in `this` but not `that` (that is,
    // the atoms that would be present in the set difference `this - that`)
    // and then compute the maximum rank/score of those atoms.

    Index thisCount = this->m_expandedAtoms.getCount();
    Index thatCount = that.m_expandedAtoms.getCount();
    Index thisIndex = 0;
    Index thatIndex = 0;
    for(;;)
    {
        if(thisIndex == thisCount) break;
        if(thatIndex == thatCount) break;

        auto thisAtom = this->m_expandedAtoms[thisIndex];
        auto thatAtom = that.m_expandedAtoms[thatIndex];

        if( thisAtom == thatAtom )
        {
            thisIndex++;
            thatIndex++;
            continue;
        }

        if( thisAtom < thatAtom )
        {
            // `thisAtom` is not present in `that`, so it
            // should contribute to our ranking of the difference.
            //
            auto thisAtomInfo = _getInfo(thisAtom);
            auto thisAtomRank = thisAtomInfo.rank;

            if( thisAtomRank > score )
            {
                score = thisAtomRank;
            }

            thisIndex++;
        }
        else
        {
            SLANG_ASSERT(thisAtom > thatAtom);
            thatIndex++;
        }
    }
    return score;
}


bool CapabilityConjunctionSet::operator==(CapabilityConjunctionSet const& other) const
{
    return m_expandedAtoms == other.m_expandedAtoms;
}

bool CapabilityConjunctionSet::operator<(CapabilityConjunctionSet const& that) const
{
    for (Index i = 0; i < Math::Min(m_expandedAtoms.getCount(), that.m_expandedAtoms.getCount()); i++)
    {
        if (m_expandedAtoms[i] < that.m_expandedAtoms[i])
            return true;
        else if (m_expandedAtoms[i] > that.m_expandedAtoms[i])
            return false;
    }
    return m_expandedAtoms.getCount() < that.m_expandedAtoms.getCount();
}


CapabilitySet::CapabilitySet()
{}

CapabilitySet::CapabilitySet(Int atomCount, CapabilityName const* atoms)
{
    for (Int i = 0; i < atomCount; i++)
        addCapability(atoms[i]);
}

CapabilitySet::CapabilitySet(CapabilityName atom)
{
    auto info = _getInfo(atom);
    for (auto conjunction : info.canonicalRepresentation)
    {
        CapabilityConjunctionSet set;
        for (auto atomName : conjunction)
            set.getExpandedAtoms().add(asAtom(atomName));
        m_conjunctions.add(_Move(set));
    }
}

CapabilitySet::CapabilitySet(List<CapabilityName> const& atoms)
{
    for (auto atom : atoms)
        addCapability(atom);
}

CapabilitySet CapabilitySet::makeEmpty()
{
    return CapabilitySet();
}

CapabilitySet CapabilitySet::makeInvalid()
{
    // An invalid capability set will always be a singleton
    // set of the `Invalid` atom, and we will construct
    // the set directly rather than use the more expensive
    // logic in `_init()`.
    //
    CapabilitySet result;
    result.m_conjunctions.add(CapabilityConjunctionSet(CapabilityAtom::Invalid));
    return result;
}

void CapabilitySet::addCapability(CapabilityName name)
{
    join(CapabilitySet(name));
}

bool CapabilitySet::isEmpty() const
{
    return m_conjunctions.getCount() == 0;
}

bool CapabilitySet::isInvalid() const
{
    return m_conjunctions.getCount() == 1 && m_conjunctions[0].isInvalid();
}

bool CapabilitySet::isIncompatibleWith(CapabilityAtom other) const
{
    if (isEmpty())
        return false;

    // If all conjunctions are incompatible with the atom, then we are incompatible.
    for (auto& c : m_conjunctions)
        if (!c.isIncompatibleWith(other))
            return false;
    return true;
}

bool CapabilitySet::isIncompatibleWith(CapabilityName other) const
{
    if (isEmpty())
        return false;
    auto otherSet = CapabilitySet(other);
    return isIncompatibleWith(otherSet);
}

bool CapabilitySet::isIncompatibleWith(CapabilityConjunctionSet const& other) const
{
    if (isEmpty())
        return false;

    // If all conjunctions are incompatible with the atom, then we are incompatible.
    for (auto& c : m_conjunctions)
        if (!c.isIncompatibleWith(other))
            return false;
    return true;
}

bool CapabilitySet::isIncompatibleWith(CapabilitySet const& other) const
{
    if (isEmpty())
        return false;
    if (other.isEmpty())
        return false;

    // If all conjunctions in other are incompatible with the this set, then we are incompatible.
    for (auto& oc : other.m_conjunctions)
        for (auto& c : m_conjunctions)
            if (!c.isIncompatibleWith(oc))
                return false;
    return true;
}

bool CapabilitySet::implies(CapabilityAtom atom) const
{
    if (isEmpty())
        return false;

    for (auto& c : m_conjunctions)
        if (c.implies(atom))
            return true;

    return false;
}

bool CapabilitySet::implies(const CapabilityConjunctionSet& set) const
{
    if (isEmpty())
        return false;

    for (auto& c : m_conjunctions)
        if (c.implies(set))
            return true;

    return false;
}

bool CapabilitySet::implies(CapabilitySet const& other) const
{
    // x implies (c | d) only if (x implies c) and (x implies d).
    if (other.isEmpty())
        return true;
    for (auto& c : other.m_conjunctions)
        if (!implies(c))
            return false;
    return true;
}

bool CapabilitySet::operator==(CapabilitySet const& that) const
{
    return m_conjunctions == that.m_conjunctions;
}

void CapabilitySet::calcCompactedAtoms(List<List<CapabilityAtom>>& outAtoms) const
{
    for (auto& c : m_conjunctions)
    {
        List<CapabilityAtom> atoms;
        c.calcCompactedAtoms(atoms);
        outAtoms.add(atoms);
    }
}

void CapabilitySet::join(const CapabilitySet& other)
{
    if (isEmpty() || other.isInvalid())
    {
        *this = other;
        return;
    }
    if (isInvalid())
        return;
    if (other.isEmpty())
        return;

    List<CapabilityConjunctionSet> resultSet;
    for (auto& thatConjunction : other.m_conjunctions)
    {
        for (auto& thisConjunction : m_conjunctions)
        {
            if (thisConjunction.isIncompatibleWith(thatConjunction))
                continue;

            CapabilityConjunctionSet conjunction;
            CapabilityConjunctionSet *conjunctionToAdd = nullptr;

            // Add atoms from thatConjunction that are not existant in thisConjunction.
            for (auto atom : thatConjunction.getExpandedAtoms())
            {
                if (thisConjunction.getExpandedAtoms().binarySearch(atom, CapabilityAtomComparator()) == -1)
                {
                    conjunction.getExpandedAtoms().add(atom);
                }
            }

            if (conjunction.getExpandedAtoms().getCount() != 0)
            {
                // If we find any capabilities in thatConjunction that is missing from thisConjunction,
                // create a new ConjunctionSet that contains atoms from both, and add it to the disjunction set.
                conjunction.getExpandedAtoms().addRange(thisConjunction.getExpandedAtoms());
                conjunction.getExpandedAtoms().sort();
                conjunctionToAdd = &conjunction;
            }
            else
            {
                // Otherwise, thisConjunction implies thatConjunction, so we just add thisConjunction to resultSet.
                conjunctionToAdd = &thisConjunction;
            }
            // We add conjunctionToAdd to resultSet only if it does not imply any existing conjunctions.
            // For example, if `resultSet` is (a), and conjunctionToAdd is (ab), then we don't want to add the conjunction
            // to form (a | ab) because that would reduce to (a).
            bool skipAdd = false;
            for (auto& c : resultSet)
            {
                if (conjunctionToAdd->implies(c))
                {
                    skipAdd = true;
                    break;
                }
            }
            if (!skipAdd)
            {
                // Once we added the new conjunction, any existing conjunctions that implies the new one can be
                // removed.
                // For example, if resultSet was (ab), and we are adding (a), the result should be just (a).
                for (Index i = 0; i < resultSet.getCount();)
                {
                    if (resultSet[i].implies(*conjunctionToAdd))
                    {
                        resultSet.fastRemoveAt(i);
                    }
                    else
                    {
                        i++;
                    }
                }
                resultSet.add(*conjunctionToAdd);
            }
        }
    }
    m_conjunctions = _Move(resultSet);

    // Make sure conjunctions are sorted so equality tests are trivial.
    m_conjunctions.sort();
}

bool CapabilitySet::isBetterForTarget(CapabilitySet const& that, CapabilitySet const& targetCaps) const
{
    if (targetCaps.isIncompatibleWith(*this))
        return false;
    if (targetCaps.isIncompatibleWith(that))
        return true;

    ArrayView<CapabilityConjunctionSet> thisSets = m_conjunctions.getArrayView();
    ArrayView<CapabilityConjunctionSet> thatSets = that.m_conjunctions.getArrayView();
    CapabilityConjunctionSet emtpySet = CapabilityConjunctionSet::makeEmpty();

    if (isEmpty())
        thisSets = makeArrayViewSingle(emtpySet);
    if (that.isEmpty())
        thatSets = makeArrayViewSingle(emtpySet);

    // It is hard to think about what it means exactly to compare a general disjunction set to another with regard
    // to a target that itself is also a disjunction set.
    // Instead of trying to find a meaning for the general case, we just want to extend the logic
    // for conjunction sets to disjunction sets in a way that common situations are handled correctly.
    // Note that when we reach here, most of these sets are likely to contain only one conjunction, so
    // we just need to make sure the more general logic here yields correct result for that case.
    // 
    // Right now, we define betterness for disjunctions as follows:
    // A capability set X is determined to be better for a target T than capability set Y,
    // if we find a conjunction A in X and a conjunction B in Y and a conjunction C in T such that
    // A is better then B for target C.
    //
    struct ViableConjunctionIndex
    {
        Index index;
        UIntSet targetConjunctionIndices;
    };
    auto getViableConjunction = [&](ArrayView<CapabilityConjunctionSet> set, List<ViableConjunctionIndex>& outList)
        {
            for (Index i = 0; i < set.getCount(); i++)
            {
                auto& conjunction = set[i];
                ViableConjunctionIndex viableConjunction;
                viableConjunction.index = i;
                for (Index j = 0; j < targetCaps.m_conjunctions.getCount(); j++)
                {
                    auto& targetConjunction = targetCaps.m_conjunctions[j];
                    if (conjunction.isIncompatibleWith(targetConjunction))
                        continue;
                    viableConjunction.targetConjunctionIndices.add(j);
                }
                if (!viableConjunction.targetConjunctionIndices.isEmpty())
                {
                    outList.add(viableConjunction);
                }
            }
        };
    List<ViableConjunctionIndex> viableConjunctionsThis;
    List<ViableConjunctionIndex> viableConjunctionsThat;

    getViableConjunction(thisSets, viableConjunctionsThis);
    getViableConjunction(thatSets, viableConjunctionsThat);
    
    for (auto& thisConjunctionIndex : viableConjunctionsThis)
    {
        auto& thisConjunction = thisSets[thisConjunctionIndex.index];
        for (auto& thatConjunctionIndex : viableConjunctionsThat)
        {
            auto& thatConjunction = thatSets[thatConjunctionIndex.index];
            UIntSet intersection = thisConjunctionIndex.targetConjunctionIndices;
            intersection.intersectWith(thatConjunctionIndex.targetConjunctionIndices);
            if (!intersection.isEmpty())
            {
                for (Index targetConjunctionIndex = 0; targetConjunctionIndex < targetCaps.m_conjunctions.getCount(); targetConjunctionIndex++)
                {
                    if (!intersection.contains((UInt)targetConjunctionIndex))
                        continue;
                    if (thisConjunction.isBetterForTarget(thatConjunction, targetCaps.m_conjunctions[targetConjunctionIndex]))
                    {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

}
back to top