summaryrefslogtreecommitdiffstats
path: root/source/slang/slang-capability.cpp
diff options
context:
space:
mode:
authorArielG-NV <159081215+ArielG-NV@users.noreply.github.com>2025-08-08 13:19:25 -0700
committerGitHub <noreply@github.com>2025-08-08 20:19:25 +0000
commit07f21ee31b5f427edb72d5578f713b3da3f3b96f (patch)
tree777df480b51f488a296bcf0c231afc3cfff2afdc /source/slang/slang-capability.cpp
parent719772c01a8ee8afa81cded249d6a51e33e17d8d (diff)
Error if super-type capabilities are a super-set of sub-type (#7452)
Fixes: #7410 Changes: 1. super-type capabilities must be a super-set of sub-type capabilities (and support the same shader stages/targets) * InheritanceDecl visits super-type to inherit it's capabilities; validate InheritanceDecl capabilities against sub-type * visit all container decl's with a default case * clean up functionDeclBase visitor * Simplify `diagnoseUndeclaredCapability` by moving logic into capability checking (more correct*) 3. added changed behavior to documentation 4. fixed some incorrect capabilities 5. **we do not** diagnose capability errors on interface requirement-to-implementation if both lack explicit capability requirements. This change is to work around a slangpy regression (test case for the failing situation is in `tests\language-feature\capability\capability-interface-extension-1.slang`), Note: maybe for slang-2026 we don't do this? 6. requirement & implementation must support the same shader stage/target. This was changed because otherwise we can have cases where `X` inherits from `Y`, but `Y` is only expected to be used in `glsl` whilst `X` is expected to be used in `hlsl | glsl` 7. removed `tests/language-feature/capability/capabilitySimplification3.slang` because it tests nothing special (redundant) Note: not using rebase due to separate branches depending on this PR --------- Co-authored-by: slangbot <186143334+slangbot@users.noreply.github.com>
Diffstat (limited to 'source/slang/slang-capability.cpp')
-rw-r--r--source/slang/slang-capability.cpp131
1 files changed, 107 insertions, 24 deletions
diff --git a/source/slang/slang-capability.cpp b/source/slang/slang-capability.cpp
index a2fef9f8a..a965ecd93 100644
--- a/source/slang/slang-capability.cpp
+++ b/source/slang/slang-capability.cpp
@@ -269,6 +269,22 @@ CapabilityAtomSet CapabilityAtomSet::newSetWithoutImpliedAtoms() const
//// CapabiltySet
+CapabilityAtomSet getTargetAtomsInSet(const CapabilitySet& set)
+{
+ CapabilityAtomSet out;
+ for (auto i : set.getCapabilityTargetSets())
+ out.add((UInt)i.first);
+ return out;
+}
+
+CapabilityAtomSet getStageAtomsInSet(const CapabilityTargetSet& set)
+{
+ CapabilityAtomSet out;
+ for (auto i : set.getShaderStageSets())
+ out.add((UInt)i.first);
+ return out;
+}
+
CapabilityAtom getTargetAtomInSet(const CapabilityAtomSet& atomSet)
{
auto targetSet = getAtomSetOfTargets();
@@ -959,56 +975,117 @@ CapabilitySet::AtomSets::Iterator CapabilitySet::getAtomSets() const
return CapabilitySet::AtomSets::Iterator(&this->getCapabilityTargetSets()).begin();
}
-bool CapabilitySet::checkCapabilityRequirement(
+void CapabilitySet::checkCapabilityRequirement(
+ CheckCapabilityRequirementOptions options,
CapabilitySet const& available,
CapabilitySet const& required,
- CapabilityAtomSet& outFailedAvailableSet)
+ CapabilityAtomSet& outFailedAvailableSet,
+ CheckCapabilityRequirementResult& result)
{
- // Requirements x are met by available disjoint capabilities (a | b) iff
+ // 'required' capabilities x are met by 'available' disjoint capabilities (a | b) iff
// both 'a' satisfies x and 'b' satisfies x.
// If we have a caller function F() decorated with:
// [require(hlsl, _sm_6_3)] [require(spirv, _spv_ray_tracing)] void F() { g(); }
// We'd better make sure that `g()` can be compiled with both (hlsl+_sm_6_3) and
// (spirv+_spv_ray_tracing) capability sets. In this method, F()'s capability declaration is
// represented by `available`, and g()'s capability is represented by `required`. We will check
- // that for every capability conjunction X of F(), there is one capability conjunction Y in g()
+ // that for every capability conjunction X of F(), there is a capability conjunction Y in g()
// such that X implies Y.
//
- // if empty there is no body, all capabilities are supported.
- if (required.isEmpty())
- return true;
+ // If empty, all capabilities are supported.
+ // Either, we require no capabilities (return true)
+ // or we have no capability requirements (return true)
+ if (required.isEmpty() || available.isEmpty())
+ {
+ result = CheckCapabilityRequirementResult::AvailableIsASuperSetToRequired;
+ return;
+ }
+ // invalid isn't a fail because the capabilities already threw an error.
if (required.isInvalid())
{
outFailedAvailableSet.add((UInt)CapabilityAtom::Invalid);
- return false;
+ result = CheckCapabilityRequirementResult::AvailableIsASuperSetToRequired;
+ return;
}
- // If F's capability is empty, we can satisfy any non-empty requirements.
- //
- if (available.isEmpty() && !required.isEmpty())
- return false;
+ auto availableTargetSets = available.getCapabilityTargetSets();
+ auto requiredTargetSets = required.getCapabilityTargetSets();
+ if (options == CheckCapabilityRequirementOptions::MustHaveEqualAbstractAtoms)
+ {
+ // If we have a mismatch in capability-target count we clearly have a
+ // mismatch and will fail
+ auto availableTargetSetsCount = availableTargetSets.getCount();
+ auto requiredTargetSetsCount = requiredTargetSets.getCount();
+ if (availableTargetSetsCount != requiredTargetSetsCount)
+ {
+ auto availableTargets = getTargetAtomsInSet(available);
+ auto requiredTargets = getTargetAtomsInSet(required);
+ if (requiredTargetSetsCount > availableTargetSetsCount)
+ {
+ result = CheckCapabilityRequirementResult::AvailableIsNotASuperSetToRequired;
+ requiredTargets.subtractWith((UIntSet)availableTargets);
+ outFailedAvailableSet.add((UIntSet)requiredTargets);
+ }
+ else
+ {
+ result = CheckCapabilityRequirementResult::RequiredIsMissingAbstractAtoms;
+ availableTargets.subtractWith((UIntSet)requiredTargets);
+ outFailedAvailableSet.add((UIntSet)availableTargets);
+ }
+ return;
+ }
+ }
- // if all sets in `available` are not a super-set to at least 1 `required` set, then we have an
- // err
- for (auto& availableTarget : available.m_targetSets)
+ // if all sets in `available` are not a superset to `required` then we have an
+ // error.
+ for (auto& availableTarget : availableTargetSets)
{
- auto reqTarget = required.m_targetSets.tryGetValue(availableTarget.first);
+ auto reqTarget = requiredTargetSets.tryGetValue(availableTarget.first);
if (!reqTarget)
{
outFailedAvailableSet.add((UInt)availableTarget.first);
- return false;
+ result = CheckCapabilityRequirementResult::RequiredIsMissingAbstractAtoms;
+ return;
}
- for (auto& availableStage : availableTarget.second.shaderStageSets)
+ if (options == CheckCapabilityRequirementOptions::MustHaveEqualAbstractAtoms)
{
- auto reqStage = reqTarget->shaderStageSets.tryGetValue(availableStage.first);
+ // If we have a mismatch in capability-stage count we clearly have a
+ // mismatch and will fail
+ auto availableStageSetsCount = availableTarget.second.getShaderStageSets().getCount();
+ auto requiredStageSetsCount = reqTarget->getShaderStageSets().getCount();
+ if (availableStageSetsCount != requiredStageSetsCount)
+ {
+ auto availableStages = getStageAtomsInSet(availableTarget.second);
+ auto requiredStages = getStageAtomsInSet(*reqTarget);
+
+ if (requiredStageSetsCount > availableStageSetsCount)
+ {
+ result = CheckCapabilityRequirementResult::AvailableIsNotASuperSetToRequired;
+ requiredStages.subtractWith((UIntSet)availableStages);
+ outFailedAvailableSet.add((UIntSet)requiredStages);
+ }
+ else
+ {
+ result = CheckCapabilityRequirementResult::RequiredIsMissingAbstractAtoms;
+ availableStages.subtractWith((UIntSet)requiredStages);
+ outFailedAvailableSet.add((UIntSet)availableStages);
+ }
+ return;
+ }
+ }
+
+ for (auto& availableStage : availableTarget.second.getShaderStageSets())
+ {
+ auto reqStage = reqTarget->getShaderStageSets().tryGetValue(availableStage.first);
if (!reqStage)
{
outFailedAvailableSet.add((UInt)availableStage.first);
- return false;
+ result = CheckCapabilityRequirementResult::RequiredIsMissingAbstractAtoms;
+ return;
}
const CapabilityAtomSet* lastBadStage = nullptr;
@@ -1020,7 +1097,7 @@ bool CapabilitySet::checkCapabilityRequirement(
{
const auto& reqStageSet = reqStage->atomSet.value();
if (availableStageSet.contains(reqStageSet))
- break;
+ continue;
else
lastBadStage = &reqStageSet;
}
@@ -1031,13 +1108,19 @@ bool CapabilitySet::checkCapabilityRequirement(
outFailedAvailableSet,
*lastBadStage,
availableStageSet);
- return false;
+
+ // Not a failiure if nothing is missing
+ if (outFailedAvailableSet.isEmpty())
+ continue;
+ result = CheckCapabilityRequirementResult::AvailableIsNotASuperSetToRequired;
+ return;
}
}
}
}
- return true;
+ result = CheckCapabilityRequirementResult::AvailableIsASuperSetToRequired;
+ return;
}
/// Converts spirv version atom to the glsl_spirv equivlent. If not possible, Invalid is returned
@@ -1097,7 +1180,7 @@ UnownedStringSlice capabilityNameToStringWithoutPrefix(CapabilityName capability
return name;
}
-void printDiagnosticArg(StringBuilder& sb, const CapabilityAtomSet atomSet)
+void printDiagnosticArg(StringBuilder& sb, const CapabilityAtomSet& atomSet)
{
bool isFirst = true;
for (auto atom : atomSet.newSetWithoutImpliedAtoms())