diff options
| author | ArielG-NV <159081215+ArielG-NV@users.noreply.github.com> | 2025-08-08 13:19:25 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-08-08 20:19:25 +0000 |
| commit | 07f21ee31b5f427edb72d5578f713b3da3f3b96f (patch) | |
| tree | 777df480b51f488a296bcf0c231afc3cfff2afdc /source/slang/slang-capability.cpp | |
| parent | 719772c01a8ee8afa81cded249d6a51e33e17d8d (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.cpp | 131 |
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()) |
