summaryrefslogtreecommitdiffstats
path: root/source/slang/slang-capability.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/slang/slang-capability.cpp')
-rw-r--r--source/slang/slang-capability.cpp428
1 files changed, 428 insertions, 0 deletions
diff --git a/source/slang/slang-capability.cpp b/source/slang/slang-capability.cpp
new file mode 100644
index 000000000..7b4361a58
--- /dev/null
+++ b/source/slang/slang-capability.cpp
@@ -0,0 +1,428 @@
+// slang-capability.cpp
+#include "slang-capability.h"
+
+// This file implements the core of the "capability" system.
+
+namespace Slang
+{
+
+//
+// CapabilityAtom
+//
+
+// We are going to divide capabilities into a few categories,
+// which will be represented as flags for now.
+//
+// Every capability will be either concrete or abstract.
+// An abstract capability basically represents a category
+// of related capabilities that all fill a similar role.
+// For example, we could have an abstract capability that
+// represents "stages" and then the concrete capabilities
+// `vertex`, `fragment`, etc. would inherit from it.
+//
+// Abstract capabilities are critical in our model for
+// knowing when two capabilities are fundamentally incompatible.
+// For example, it is meaningless to compile code for both
+// the `vertex` and `fragment` capabilities at the same time,
+// because no target processor supports both at once.
+//
+// TODO: It is possible that instead of flags this could simply
+// identify a "kind" of atom, with two different states.
+//
+// TODO: It is likely that in a future change we will want to
+// add a third case here for "alias" capabilities, which are
+// pseudo-atomic capabilities that are just equivalent to
+// the set of their bases.
+//
+typedef uint32_t CapabilityAtomFlags;
+enum : CapabilityAtomFlags
+{
+ kCapabilityAtomFlags_Concrete = 0,
+ kCapabilityAtomFlags_Abstract = 1 << 0,
+};
+
+// 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;
+
+ /// Flags to determine if the capability is concrete-vs-abstract, etc.
+ CapabilityAtomFlags flags;
+ CapabilityAtom bases[4];
+};
+//
+// The array is going to be sized to include an entry for `CapabilityAtom::Invalid`
+// which as a value of -1, so we need to size the array one larger than the `Count`
+// value.
+//
+static const CapabilityAtomInfo kCapabilityAtoms[Int(CapabilityAtom::Count) + 1] =
+{
+ { "invalid", 0, { CapabilityAtom::Invalid, CapabilityAtom::Invalid, CapabilityAtom::Invalid, CapabilityAtom::Invalid } },
+
+#define SLANG_CAPABILITY_ATOM(ENUMERATOR, NAME, FLAGS, BASE0, BASE1, BASE2, BASE3) \
+ { #NAME, kCapabilityAtomFlags_##FLAGS, { CapabilityAtom::BASE0, CapabilityAtom::BASE1, CapabilityAtom::BASE2, CapabilityAtom::BASE3 } },
+#include "slang-capability-defs.h"
+};
+
+ /// Get the extended information structure for the given capability `atom`
+static CapabilityAtomInfo const& _getInfo(CapabilityAtom atom)
+{
+ SLANG_ASSERT(Int(atom) < Int(CapabilityAtom::Count));
+ return kCapabilityAtoms[Int(atom) + 1];
+}
+
+// One capability set or capability atom A implies another set/atom B
+// if any target that supports all of the atoms in A must also support
+// all of those in B.
+
+ /// Does `thisAtom` imply `thatAtom`?
+static bool _implies(CapabilityAtom thisAtom, CapabilityAtom thatAtom)
+{
+ // When looking at atoms, the immediate easy case is when
+ // the two atoms are the same: an atomic capability always
+ // implies itself.
+ //
+ if(thisAtom == thatAtom)
+ return true;
+
+ // Otherwise, we want to look at the bases of `thisAtom`
+ // to see if any of them imply `thatAtom`, since `thisAtom`
+ // implies each of its bases.
+ //
+ auto& thisAtomInfo = _getInfo(thisAtom);
+ for( auto thisAtomBase : thisAtomInfo.bases )
+ {
+ // The lists of bases are currently using `Invalid` as
+ // a sentinel value to terminate them, so we need to
+ // bail out of the loop when we see the sentinel.
+ //
+ if(thisAtomBase == CapabilityAtom::Invalid)
+ break;
+
+ if(_implies(thisAtomBase, thatAtom))
+ return true;
+ }
+
+ return false;
+}
+
+ /// Does `base` have any abstract capabilities in common with `otherAtom`
+ ///
+ /// This subroutine is a helper for `_isIncompatible`.
+static bool _hasAbstractBaseInCommon(CapabilityAtom base, CapabilityAtom otherAtom)
+{
+ // First we check the case where `base` itself is an abstract
+ // capability atom.
+ //
+ auto& baseAtomInfo = _getInfo(base);
+ if(baseAtomInfo.flags & kCapabilityAtomFlags_Abstract)
+ {
+ // If `base` is abstract, and `otherAtom` implies `base`,
+ // then that means that `otherAtom` includes one or
+ // more atoms that inherit from `base`, and thus the
+ // two have an abstract base in common.
+ //
+ if( _implies(otherAtom, base) )
+ return true;
+ }
+
+ // If `base` itself has bases, then we want to check if any
+ // of *those* are abstract bases that overlap with `otherAtom`.
+ //
+ for( auto baseBase : baseAtomInfo.bases )
+ {
+ if(baseBase == CapabilityAtom::Invalid)
+ break;
+
+ if(_hasAbstractBaseInCommon(baseBase, otherAtom))
+ return true;
+ }
+
+ // If we didn't manage to find any overlaps, then we conclude
+ // that there are no shared abstract bases.
+ //
+ return false;
+}
+
+ /// Is `thisAtom` incompatible with `thatAtom` (such that no target could ever support both at once)
+static bool _isIncompatible(CapabilityAtom thisAtom, CapabilityAtom thatAtom)
+{
+ // If either atom implies the other, then they aren't incompatible.
+ //
+ // For example, if there is an atom representing `sm_5_1` that inherits
+ // from an atom representing `sm_5_0`, then clearly the two aren't
+ // in any way incompatible (a single target can support both).
+ //
+ if(_implies(thisAtom, thatAtom) || _implies(thatAtom, thisAtom))
+ return false;
+
+ // If the two atoms are not in an inheritance relationship, then one of
+ // a few cases can apply:
+ //
+ // * They have no common bases; in this case they are compatible.
+ // An example would be `vertex` and `sm_5_0`.
+ //
+ // * They have a common base, but it is not marked abstract; in
+ // this case they are compatible. E.g., two GLSL extensions that
+ // both inherit from the `glsl` capability should not conflict.
+ //
+ // * They have a common base that is marked abstract; in this
+ // case they are incompatible. An example would be `vertex`
+ // and `fragment` both inheriting from the abstract atom
+ // `__stage`.
+ //
+ // To summarize the above list, we note that two atoms are
+ // incompatible with they have an abstract base in common.
+ //
+ return _hasAbstractBaseInCommon(thisAtom, thatAtom);
+
+ // TODO: The above logic is a bit off, but in a way that doesn't
+ // matter just yet.
+ //
+ // We currently have capabilities like:
+ //
+ // abstract capability __target;
+ // capability hlsl : __target;
+ // capability glsl : __target;
+ //
+ // In this case it is clear that `hlsl` and `glsl` should
+ // be incompatible, and that the rules as implemented
+ // make that the case.
+ //
+ // A problem arises when we start to add things like extensions:
+ //
+ // capability EXT_cool_thing : glsl;
+ // capability EXT_other_stuff : glsl;
+ //
+ // In this case, it also seems clear that `EXT_cool_thing`
+ // and `EXT_other_stuff` should be mutually compatible.
+ // However, with the rules implemented here right now, they
+ // would be found incompatible because they share the
+ // abstract base `__target`.
+ //
+ // In this specific case, we know that the relationship
+ // between the extensions is fine because they both inherit
+ // from `__target` *through* the concrete atom `glsl`.
+ //
+ // Before adding capabilities that represent optional
+ // extensions like this we need to codify the semantics
+ // for how incompatibility checks should work in terms
+ // of the inheritance graph of capability atoms.
+}
+
+CapabilityAtom findCapabilityAtom(UnownedStringSlice const& name)
+{
+ // For now we are implementing a linear search over the
+ // array of capability atoms to perform name lookup.
+ //
+ for( Index i = 0; i < Index(CapabilityAtom::Count); ++i )
+ {
+ // Note: using `_getInfo` here instead of accessing
+ // the `kCapabilityAtoms` array directly lets us
+ // avoid dealing with the offset-by-one indexing
+ // choice.
+ //
+ auto& capInfo = _getInfo(CapabilityAtom(i));
+ if(name == UnownedTerminatedStringSlice(capInfo.name))
+ return CapabilityAtom(i);
+ }
+ return CapabilityAtom::Invalid;
+}
+
+//
+// CapabilitySet
+//
+
+// The current design choice in `CapabilitySet` is that it blindly
+// stores exactly the atoms it is told to, without any up-front
+// processing.
+//
+// This choice has some down-sides, and there are other representations
+// that could be much nicer in the future. Possible improcements include:
+//
+// * The list of atoms could be *expanded* so that if it contains atom A
+// and atom A implies atom B, then the list should also include B.
+//
+// * The list of atoms could be *minimized*, such that if atom A implies
+// atom B, then any list that contains A does not include B (both
+// expanded and minimized lists have different benefits).
+//
+// * The list of atoms could be deduplicated.
+//
+// * The list of atoms could be sorted.
+//
+// * The lists could be deduplicated and cached in some central place
+// (the like the session) so that repreated attempts to create the
+// same capability sets return the same objects.
+//
+// In some parts of the code below we will call out how these improvements
+// could affect the algorithms used.
+
+// Given our simple choices right now, the constructors for `CapabilitySet`
+// are all straightforward: just adding the right atoms to the list.
+
+CapabilitySet::CapabilitySet()
+{}
+
+CapabilitySet::CapabilitySet(Int atomCount, CapabilityAtom const* atoms)
+{
+ m_atoms.addRange(atoms, atomCount);
+}
+
+CapabilitySet::CapabilitySet(CapabilityAtom atom)
+{
+ m_atoms.add(atom);
+}
+
+CapabilitySet::CapabilitySet(List<CapabilityAtom> const& atoms)
+ : m_atoms(atoms)
+{}
+
+
+CapabilitySet CapabilitySet::makeEmpty()
+{
+ return CapabilitySet();
+}
+
+CapabilitySet CapabilitySet::makeInvalid()
+{
+ return CapabilitySet(CapabilityAtom::Invalid);
+}
+
+bool CapabilitySet::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_atoms.getCount() == 0;
+}
+
+bool CapabilitySet::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_atoms.getCount() != 1) return false;
+ return m_atoms[0] == CapabilityAtom::Invalid;
+}
+
+bool CapabilitySet::isIncompatibleWith(CapabilityAtom that) const
+{
+ // We know that capabilities that are in an inheritnace
+ // relationship with one another can't be incompatible.
+ //
+ if(this->implies(that) || CapabilitySet(that).implies(*this))
+ return false;
+
+ // Othwerise, we want to perform a check for each of the
+ // atoms in this set, whether it is incompatible with any
+ // of the atoms in the other set (which in this case is one atom).
+ //
+ for( auto thisAtom : this->m_atoms )
+ {
+ if(_isIncompatible(thisAtom, that))
+ return true;
+ }
+
+ return false;
+}
+
+bool CapabilitySet::isIncompatibleWith(CapabilitySet const& that) const
+{
+ // We need to look at the atoms in `this` that are not
+ // present in `that`, and vice versa. For each such atom
+ // we will check if it is incompatible with the other, by
+ // virtue of the other already including a concrete atom
+ // that cannot co-exist with it.
+ //
+ for( auto thisAtom : this->m_atoms )
+ {
+ if(that.isIncompatibleWith(thisAtom))
+ return true;
+ }
+ for( auto thatAtom : that.m_atoms )
+ {
+ if(this->isIncompatibleWith(thatAtom))
+ return true;
+ }
+ return false;
+
+ // TODO: If we had a representation that stored a minified,
+ // sorted, deduplicated list of atoms, then it would be easy
+ // to iterate over the two lists in tandem and identify any
+ // element that is present in one list but not the other.
+ //
+ // Those elements would be the candidates that could cause
+ // incompatiblity, so that we wouldn't need to perform
+ // the check on each atom like we do above.
+}
+
+bool CapabilitySet::implies(CapabilitySet const& that) const
+{
+ // This capability set implies `other` if for every atom in `other`,
+ // that atom is present in this sets list of atoms or it is
+ // implies by something in the list of atoms.
+ //
+ for( auto atom : that.m_atoms )
+ {
+ if(!this->implies(atom))
+ return false;
+ }
+ return true;
+
+ // TODO: If we had a representation that stored an expanded
+ // sorted, deduplicated list of atoms, then we could
+ // check the `implies` relationship by scanning through
+ // the two lists in tandem and identifying any element
+ // in the `that` list that isn't in the `this` list.
+ // Such elements would indicate that `that` is not a subset
+ // of `this`.
+}
+
+
+bool CapabilitySet::implies(CapabilityAtom atom) const
+{
+ // If our list of explicit atoms contains `atom`, then
+ // we definitely imply it.
+ //
+ // TODO: If we stored our atom lists sorted, then
+ // this operation could be logarithmic rather than
+ // linear.
+ //
+ if(m_atoms.contains(atom))
+ return true;
+
+ // If any of our atoms implies `atom` then we
+ // also imply it.
+ //
+ // TODO: If we stored an expanded atom list, then
+ // this recursion could be skipped completely, since
+ // the containment check above would cover inheirtance
+ // relationships too.
+ //
+ for( auto thisAtom : m_atoms )
+ {
+ if(_implies(thisAtom, atom))
+ return true;
+ }
+
+ return false;
+}
+
+bool CapabilitySet::operator==(CapabilitySet const& other) const
+{
+ return this->implies(other) && other.implies(*this);
+}
+
+}