From 509bfd8bbaaf021507c4045b5fd9eaf43276dc0a Mon Sep 17 00:00:00 2001 From: ArielG-NV <159081215+ArielG-NV@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:36:38 -0400 Subject: [PATCH] Simplify `CapabilitySet` Diagnostic Printing (#4678) Fixes: #4675 Fixes: #4683 Fixes: #4443 Fixes: #4585 Fixes: #4172 Made the following changes: 1. All capability diagnostic printing logic tries to simplify before printing. This means that we do not print atoms which imply another atom. 2. Do not print the `_` prefix part of atom names since it is misleading users on what they should use to solve a capability issue encountered. (`_Internal` `External` atom changes are not in this PR) 3. Bundle together printing of all sets which contain exactly the same atoms (excluding abstract atoms). This allows printing the following `vertex/fragment/hull/domain/... + glsl` instead of `vertex + glsl | fragment + glsl | hull + glsl | domain + glsl | ....` 4. Rework how entry-point errors are reported to users (example at bottom of PR comment) 5. Rework how atom-provenance data is collected to be leaner and more useful so we can rework the errors. There are 2 notable changes here: * We no longer store a list which describes where the first of an `CapabilityAtom` comes from. This heavily simplifies AST logic for the capability system. AST parsing of capabilities is much faster. The trade-off is faster AST parsing and correct AST node data for slower diagnostics if an error is found * atom-provenance data now stores a reference to an atom's use-site to provide information on **where** and **what** is wrong with user code versus only sharing **what** and not where. --- source/slang/slang-ast-base.h | 2 +- source/slang/slang-capability.cpp | 183 +++++++++++++++--- source/slang/slang-capability.h | 18 ++ source/slang/slang-check-decl.cpp | 90 ++++++--- source/slang/slang-check-impl.h | 3 +- source/slang/slang-check-shader.cpp | 29 +-- source/slang/slang-diagnostic-defs.h | 6 +- .../capability/capability7.slang | 52 +++++ .../capabilitySimplification1.slang | 34 ++++ .../capabilitySimplification2.slang | 27 +++ .../capabilitySimplification3.slang | 17 ++ 11 files changed, 387 insertions(+), 74 deletions(-) create mode 100644 tests/language-feature/capability/capability7.slang create mode 100644 tests/language-feature/capability/capabilitySimplification1.slang create mode 100644 tests/language-feature/capability/capabilitySimplification2.slang create mode 100644 tests/language-feature/capability/capabilitySimplification3.slang diff --git a/source/slang/slang-ast-base.h b/source/slang/slang-ast-base.h index b16eb5ddf8..6f3789c7e4 100644 --- a/source/slang/slang-ast-base.h +++ b/source/slang/slang-ast-base.h @@ -743,7 +743,7 @@ class Decl : public DeclBase bool isChildOf(Decl* other) const; // Track the decl reference that caused the requirement of a capability atom. - SLANG_UNREFLECTED Dictionary capabilityRequirementProvenance; + SLANG_UNREFLECTED List capabilityRequirementProvenance; private: SLANG_UNREFLECTED DeclRefBase* m_defaultDeclRef = nullptr; SLANG_UNREFLECTED Index m_defaultDeclRefEpoch = -1; diff --git a/source/slang/slang-capability.cpp b/source/slang/slang-capability.cpp index d9201dad6c..f06ccb663b 100644 --- a/source/slang/slang-capability.cpp +++ b/source/slang/slang-capability.cpp @@ -165,6 +165,57 @@ bool isCapabilityDerivedFrom(CapabilityAtom atom, CapabilityAtom base) return false; } +//CapabilityAtomSet + +CapabilityAtomSet CapabilityAtomSet::newSetWithoutImpliedAtoms() const +{ + // plan is to add all atoms which is impled (=>) another atom. + // Implying an atom appears in the form of atom1=>atom2 or atom2=>atom1. + Dictionary candidateForSimplifiedList; + CapabilityAtomSet simplifiedSet; + for (auto atom1UInt : *this) + { + CapabilityAtom atom1 = (CapabilityAtom)atom1UInt; + if (!candidateForSimplifiedList.addIfNotExists(atom1, true) + && candidateForSimplifiedList[atom1] == false) + continue; + + for (auto atom2UInt : *this) + { + if (atom1UInt == atom2UInt) + continue; + + CapabilityAtom atom2 = (CapabilityAtom)atom2UInt; + if (!candidateForSimplifiedList.addIfNotExists(atom2, true) + && candidateForSimplifiedList[atom2] == false) + continue; + + auto atomInfo1 = _getInfo(atom1).canonicalRepresentation; + auto atomInfo2 = _getInfo(atom2).canonicalRepresentation; + for (auto atomSet1 : atomInfo1) + { + for (auto atomSet2 : atomInfo2) + { + if (atomSet1->contains(*atomSet2)) + { + candidateForSimplifiedList[atom2] = false; + continue; + } + else if (atomSet2->contains(*atomSet1)) + { + candidateForSimplifiedList[atom1] = false; + continue; + } + } + } + } + } + for (auto i : candidateForSimplifiedList) + if(i.second) + simplifiedSet.add((UInt)i.first); + return simplifiedSet; +} + //// CapabiltySet CapabilityAtom getTargetAtomInSet(const CapabilityAtomSet& atomSet) @@ -897,31 +948,118 @@ void CapabilitySet::addSpirvVersionFromOtherAsGlslSpirvVersion(CapabilitySet& ot } } -void printDiagnosticArg(StringBuilder& sb, const CapabilitySet& capSet) +UnownedStringSlice capabilityNameToStringWithoutPrefix(CapabilityName capabilityName) +{ + auto name = capabilityNameToString(capabilityName); + if (name.startsWith("_")) + return name.tail(1); + return name; +} + +void printDiagnosticArg(StringBuilder& sb, const CapabilityAtomSet atomSet) +{ + bool isFirst = true; + for (auto atom : atomSet.newSetWithoutImpliedAtoms()) + { + CapabilityName formattedAtom = (CapabilityName)atom; + if (!isFirst) + sb << " + "; + sb << capabilityNameToStringWithoutPrefix(formattedAtom); + isFirst = false; + } +} + +// Collection of stages which have same atom sets to compress reprisentation of atom and stage per target +struct CompressedCapabilitySet { - bool isFirstSet = true; - for (auto& set : capSet.getAtomSets()) + /// Collection of stages which have same atom sets to compress reprisentation of atom and stage: {vertex/fragment, ... } + struct StageAndAtomSet + { + CapabilityAtomSet stages; + CapabilityAtomSet atomsWithoutStage; + }; + + auto begin() { - if (!isFirstSet) + return atomSetsOfTargets.begin(); + } + + /// Compress 1 capabilitySet into a reprisentation which merges stages that share all of their atoms for printing. + Dictionary> atomSetsOfTargets; + CompressedCapabilitySet(const CapabilitySet& capabilitySet) + { + for (auto& atomSet : capabilitySet.getAtomSets()) { - sb<< " | "; + auto target = getTargetAtomInSet(atomSet); + + auto stageInSetAtom = getStageAtomInSet(atomSet); + CapabilityAtomSet stageInSet; + stageInSet.add((UInt)stageInSetAtom); + + CapabilityAtomSet atomsWithoutStage; + CapabilityAtomSet::calcSubtract(atomsWithoutStage, atomSet, stageInSet); + if (!atomSetsOfTargets.containsKey(target)) + { + atomSetsOfTargets[target].add({ stageInSet, atomsWithoutStage }); + continue; + } + + // try to find an equivlent atom set by iterating all of the same `atomSetsOfTarget[target]` and merge these 2 together. + auto& atomSetsOfTarget = atomSetsOfTargets[target]; + for (auto& i : atomSetsOfTarget) + { + if (i.atomsWithoutStage.contains(atomsWithoutStage) && atomsWithoutStage.contains(i.atomsWithoutStage)) + { + i.stages.add((UInt)stageInSetAtom); + } + } } - bool isFirst = true; - for (auto atom : set) + for (auto& targetSets : atomSetsOfTargets) + for (auto& targetSet : targetSets.second) + targetSet.atomsWithoutStage = targetSet.atomsWithoutStage.newSetWithoutImpliedAtoms(); + } +}; + +void printDiagnosticArg(StringBuilder& sb, const CompressedCapabilitySet& capabilitySet) +{ + ////Secondly we will print our new list of atomSet's. + sb << "{"; + bool firstSet = true; + for (auto targetSets : capabilitySet.atomSetsOfTargets) + { + if(!firstSet) + sb << " || "; + for (auto targetSet : targetSets.second) { - CapabilityName formattedAtom = (CapabilityName)atom; - if (!isFirst) + bool firstStage = true; + for (auto stageAtom : targetSet.stages) + { + if (!firstStage) + sb << "/"; + printDiagnosticArg(sb, (CapabilityName)stageAtom); + firstStage = false; + } + for (auto atom : targetSet.atomsWithoutStage) { sb << " + "; + printDiagnosticArg(sb, (CapabilityName)atom); } - auto name = capabilityNameToString((CapabilityName)formattedAtom); - if (name.startsWith("_")) - name = name.tail(1); - sb << name; - isFirst = false; } - isFirstSet = false; + firstSet = false; } + sb << "}"; +} + +void printDiagnosticArg(StringBuilder& sb, const CapabilitySet& capabilitySet) +{ + // Firstly we will compress the printing of capabilities such that any atomSet + // with different abstract atoms but equal non-abstract atoms will be bundled together. + if (capabilitySet.isInvalid() || capabilitySet.isEmpty()) + { + sb << "{}"; + return; + } + printDiagnosticArg(sb, CompressedCapabilitySet(capabilitySet)); } void printDiagnosticArg(StringBuilder& sb, CapabilityAtom atom) @@ -931,20 +1069,15 @@ void printDiagnosticArg(StringBuilder& sb, CapabilityAtom atom) void printDiagnosticArg(StringBuilder& sb, CapabilityName name) { - sb << _getInfo(name).name; + sb << capabilityNameToStringWithoutPrefix(name); } void printDiagnosticArg(StringBuilder& sb, List& list) { - sb << "{"; - auto count = list.getCount(); - for(Index i = 0; i < count; i++) - { - printDiagnosticArg(sb, list[i]); - if (i + 1 != count) - sb << ", "; - } - sb << "}"; + CapabilityAtomSet set; + for (auto i : list) + set.add((UInt)i); + printDiagnosticArg(sb, set.newSetWithoutImpliedAtoms()); } diff --git a/source/slang/slang-capability.h b/source/slang/slang-capability.h index cbdf48ac6e..299e956e4d 100644 --- a/source/slang/slang-capability.h +++ b/source/slang/slang-capability.h @@ -51,6 +51,8 @@ namespace Slang struct CapabilityAtomSet : UIntSet { using UIntSet::UIntSet; + + CapabilityAtomSet newSetWithoutImpliedAtoms() const; }; struct CapabilityTargetSet; @@ -300,6 +302,22 @@ struct CapabilitySet /// Add spirv version capabilities from 'spirv CapabilityTargetSet' as glsl_spirv version capability in 'glsl CapabilityTargetSet' void addSpirvVersionFromOtherAsGlslSpirvVersion(CapabilitySet& other); + /// Gets the first valid compile-target found in the CapabilitySet + CapabilityAtom getCompileTarget() + { + if(isEmpty() || isInvalid()) + return CapabilityAtom::Invalid; + return (*m_targetSets.begin()).first; + } + + /// Gets the first valid stage found in the CapabilitySet + CapabilityAtom getTargetStage() + { + if(isEmpty() || isInvalid()) + return CapabilityAtom::Invalid; + return (*(*m_targetSets.begin()).second.shaderStageSets.begin()).first; + } + private: /// underlying data of CapabilitySet. CapabilityTargetSets m_targetSets{}; diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index 59d8ba8b38..482f15aca0 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -354,7 +354,7 @@ namespace Slang virtual void processReferencedDecl(Decl* decl) = 0; - virtual void processDeclModifiers(Decl* decl) = 0; + virtual void processDeclModifiers(Decl* decl, SourceLoc refLoc) = 0; void dispatchIfNotNull(Stmt* stmt) { @@ -464,7 +464,9 @@ namespace Slang { dispatchIfNotNull(expr->type.type); dispatchIfNotNull(expr->declRef.declRefBase); - processDeclModifiers(expr->declRef.getDecl()); + + // Pass down the callee location + processDeclModifiers(expr->declRef.getDecl(), expr->loc); } void visitStaticMemberExpr(StaticMemberExpr* expr) { @@ -10228,15 +10230,8 @@ namespace Slang decl = visitor->getParentFuncOfVisitor(); if (referencedDecl && decl) { - for (auto& capSet : nodeCaps.getAtomSets()) - { - auto elements = capSet.getElements(); - decl->capabilityRequirementProvenance.reserve(decl->capabilityRequirementProvenance.getCount()+elements.getCount()); - for (auto atom : elements) - { - decl->capabilityRequirementProvenance.addIfNotExists(atom, DeclReferenceWithLoc{ referencedDecl, referenceLoc }); - } - } + // Here we store a childDecl that added/removed capabilities from a parentDecl + decl->capabilityRequirementProvenance.add(DeclReferenceWithLoc{ referencedDecl, referenceLoc }); } }; @@ -10267,10 +10262,10 @@ namespace Slang loc = Base::sourceLocStack.getLast(); handleProcessFunc(decl, decl->inferredCapabilityRequirements, loc); } - virtual void processDeclModifiers(Decl* decl) override + virtual void processDeclModifiers(Decl* decl, SourceLoc refLoc) override { if (decl) - handleProcessFunc(decl, decl->inferredCapabilityRequirements, decl->loc); + handleProcessFunc(decl, decl->inferredCapabilityRequirements, refLoc); } void visitDiscardStmt(DiscardStmt* stmt) { @@ -10694,18 +10689,62 @@ namespace Slang return false; } - void diagnoseCapabilityProvenance(CompilerOptionSet& optionSet, DiagnosticSink* sink, Decl* decl, CapabilityAtom atomToFind, bool optionallyNeverPrintDecl) + void diagnoseMissingCapabilityProvenance(CompilerOptionSet& optionSet, DiagnosticSink* sink, Decl* decl, CapabilitySet& setToFind) + { + HashSet checkedDecls; + DeclReferenceWithLoc declWithRef; + declWithRef.referencedDecl = decl; + declWithRef.referenceLoc = (decl) ? decl->loc : SourceLoc(); + bool bottomOfProvenanceStack = false; + // Find the bottom of the atom provenance stack which fails to contain `setToFind` + while(!bottomOfProvenanceStack && declWithRef.referencedDecl) + { + bottomOfProvenanceStack = true; + for(auto& i : declWithRef.referencedDecl->capabilityRequirementProvenance) + { + if (checkedDecls.contains(i.referencedDecl)) + continue; + checkedDecls.add(i.referencedDecl); + + if(!i.referencedDecl->inferredCapabilityRequirements.implies(setToFind)) + { + // We found a source of the incompatible capability, follow this + // element inside the provenance stack until we are at the bottom + declWithRef = i; + bottomOfProvenanceStack = false; + break; + } + } + } + + if (!declWithRef.referencedDecl) + return; + + // Diagnose the use-site + maybeDiagnose(sink, optionSet, DiagnosticCategory::Capability, declWithRef.referenceLoc, Diagnostics::seeUsingOf, declWithRef.referencedDecl); + // Diagnose the definition as the problem + maybeDiagnose(sink, optionSet, DiagnosticCategory::Capability, declWithRef.referencedDecl->loc, Diagnostics::seeDefinitionOf, declWithRef.referencedDecl); + + // If we find a 'require' modifier, this is contributing to the overall capability incompatibility. + // We should hint to the user that this declaration is problematic. + if (auto requireCapabilityAttribute = declWithRef.referencedDecl->findModifier()) + maybeDiagnose(sink, optionSet, DiagnosticCategory::Capability, requireCapabilityAttribute->loc, Diagnostics::seeDeclarationOf, requireCapabilityAttribute); + } + + void diagnoseCapabilityProvenance(CompilerOptionSet& optionSet, DiagnosticSink* sink, Decl* decl, CapabilityAtom atomToFind, HashSet& printedDecls) { - HashSet printedDecls; auto thisModule = getModuleDecl(decl); Decl* declToPrint = decl; while (declToPrint) { + Decl* previousDecl = declToPrint; printedDecls.add(declToPrint); - if (auto provenance = declToPrint->capabilityRequirementProvenance.tryGetValue(atomToFind)) + for(auto& provenance : declToPrint->capabilityRequirementProvenance) { - maybeDiagnose(sink, optionSet, DiagnosticCategory::Capability, provenance->referenceLoc, Diagnostics::seeUsingOf, provenance->referencedDecl); - declToPrint = provenance->referencedDecl; + if (!provenance.referencedDecl->inferredCapabilityRequirements.implies(atomToFind)) + continue; + maybeDiagnose(sink, optionSet, DiagnosticCategory::Capability, provenance.referenceLoc, Diagnostics::seeUsingOf, provenance.referencedDecl); + declToPrint = provenance.referencedDecl; if (printedDecls.contains(declToPrint)) break; if (declToPrint->findModifier()) @@ -10718,12 +10757,10 @@ namespace Slang if (getDeclVisibility(declToPrint) == DeclVisibility::Public) break; } - else - { + if (previousDecl == declToPrint) break; - } } - if (declToPrint && !optionallyNeverPrintDecl) + if (declToPrint) { maybeDiagnose(sink, optionSet, DiagnosticCategory::Capability, declToPrint->loc, Diagnostics::seeDefinitionOf, declToPrint); } @@ -10763,10 +10800,11 @@ namespace Slang CapabilityAtomSet targetsNotUsedSet; CapabilityAtomSet::calcSubtract(targetsNotUsedSet, getAtomSetOfTargets(), failedAtomSet); + HashSet printedDecls; for (auto atom : targetsNotUsedSet) { CapabilityAtom formattedAtom = asAtom(atom); - diagnoseCapabilityProvenance(this->getOptionSet(), getSink(), decl, formattedAtom, true); + diagnoseCapabilityProvenance(this->getOptionSet(), getSink(), decl, formattedAtom, printedDecls); } return; } @@ -10786,12 +10824,14 @@ namespace Slang // We will produce all failed atoms. This is important since provenance of multiple atoms // can come from multiple referenced items in a function body. - for (auto i : failedAtomsInsideAvailableSet) + HashSet printedDecls; + auto simplifiedFailedAtomsSet = failedAtomsInsideAvailableSet.newSetWithoutImpliedAtoms(); + for (auto i : simplifiedFailedAtomsSet) { CapabilityAtom formattedAtom = asAtom(i); maybeDiagnose(getSink(), this->getOptionSet(), DiagnosticCategory::Capability, decl->loc, diagnosticInfo, decl, formattedAtom); // Print provenances. - diagnoseCapabilityProvenance(this->getOptionSet(), getSink(), decl, formattedAtom); + diagnoseCapabilityProvenance(this->getOptionSet(), getSink(), decl, formattedAtom, printedDecls); } } diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index 86655cedec..60b8b426d5 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -2868,7 +2868,8 @@ namespace Slang // texture, buffer, sampler, acceleration structure, etc. bool isOpaqueHandleType(Type* type); - void diagnoseCapabilityProvenance(CompilerOptionSet& optionSet, DiagnosticSink* sink, Decl* decl, CapabilityAtom atomToFind, bool optionallyNeverPrintDecl = false); + void diagnoseMissingCapabilityProvenance(CompilerOptionSet& optionSet, DiagnosticSink* sink, Decl* decl, CapabilitySet& setToFind); + void diagnoseCapabilityProvenance(CompilerOptionSet& optionSet, DiagnosticSink* sink, Decl* decl, CapabilityAtom atomToFind, HashSet& printedDecls); void _ensureAllDeclsRec( SemanticsDeclVisitorBase* visitor, diff --git a/source/slang/slang-check-shader.cpp b/source/slang/slang-check-shader.cpp index 3d086b189d..a802906a74 100644 --- a/source/slang/slang-check-shader.cpp +++ b/source/slang/slang-check-shader.cpp @@ -521,26 +521,15 @@ namespace Slang targetCaps.join(stageCapabilitySet); if (targetCaps.isIncompatibleWith(entryPointFuncDecl->inferredCapabilityRequirements)) { - maybeDiagnose(sink, linkage->m_optionSet, DiagnosticCategory::Capability, entryPointFuncDecl, Diagnostics::entryPointUsesUnavailableCapability, entryPointFuncDecl, entryPointFuncDecl->inferredCapabilityRequirements, targetCaps); + // Incompatable means we don't support a set of abstract atoms. + // Diagnose that we lack support for 'stage' and 'target' atoms with our provided entry-point + auto compileTarget = target->getTargetCaps().getCompileTarget(); + auto stageTarget = stageCapabilitySet.getTargetStage(); + maybeDiagnose(sink, linkage->m_optionSet, DiagnosticCategory::Capability, entryPointFuncDecl, Diagnostics::entryPointUsesUnavailableCapability, entryPointFuncDecl, compileTarget, stageTarget); - // Find out what exactly is incompatible and print out a trace of provenance to - // help user diagnose their code. - // TODO: provedence should have a way to filter out for provenance that are missing X capabilitySet from their caps, else in big functions we get junk errors - // This is specifically a problem for when a function is missing a target but otherwise has identical capabilities. - - const auto interredCapConjunctions = entryPointFuncDecl->inferredCapabilityRequirements.getAtomSets(); - const auto compileCaps = targetCaps.getAtomSets(); - if (compileCaps && interredCapConjunctions) - { - for (auto inferredAtom : *interredCapConjunctions.begin()) - { - CapabilityAtom inferredAtomFormatted = asAtom(inferredAtom); - if (!compileCaps->contains((UInt)inferredAtom)) - { - diagnoseCapabilityProvenance(linkage->m_optionSet, sink, entryPointFuncDecl, inferredAtomFormatted); - } - } - } + // Find out what is incompatible (ancestor missing a super set of 'target+stage') + CapabilitySet failedSet({ (CapabilityName)compileTarget, (CapabilityName)stageTarget }); + diagnoseMissingCapabilityProvenance(linkage->m_optionSet, sink, entryPointFuncDecl, failedSet); } else { @@ -571,6 +560,8 @@ namespace Slang entryPointFuncDecl->loc, Diagnostics::profileImplicitlyUpgraded, Diagnostics::profileImplicitlyUpgradedRestrictive, + entryPointFuncDecl, + target->getOptionSet().getProfile().getName(), addedAtoms.getElements()); } } diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index 78e37821d1..1a863351ca 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -389,7 +389,7 @@ DIAGNOSTIC(36104, Error, useOfUndeclaredCapability, "'$0' uses undeclared capabi DIAGNOSTIC(36104, Error, useOfUndeclaredCapabilityOfInterfaceRequirement, "'$0' uses capability '$1' that is missing from the interface requirement.") DIAGNOSTIC(36105, Error, unknownCapability, "unknown capability name '$0'.") DIAGNOSTIC(36106, Error, expectCapability, "expect a capability name.") -DIAGNOSTIC(36107, Error, entryPointUsesUnavailableCapability, "entrypoint '$0' requires capability '$1', which is incompatible with the current compilation target '$2'.") +DIAGNOSTIC(36107, Error, entryPointUsesUnavailableCapability, "entrypoint '$0' does not support compilation target '$1' with stage '$2'") DIAGNOSTIC(36108, Error, declHasDependenciesNotCompatibleOnTarget, "'$0' has dependencies that are not compatible on the required target '$1'.") DIAGNOSTIC(36109, Error, invalidTargetSwitchCase, "'$0' cannot be used as a target_switch case.") DIAGNOSTIC(36110, Error, stageIsIncompatibleWithCapabilityDefinition, "'$0' is defined for stage '$1', which is incompatible with the declared capability set '$2'.") @@ -737,8 +737,8 @@ DIAGNOSTIC(41001, Error, recursiveType, "type '$0' contains cyclic reference to DIAGNOSTIC(41010, Warning, missingReturn, "control flow may reach end of non-'void' function") DIAGNOSTIC(41011, Error, profileIncompatibleWithTargetSwitch, "__target_switch has no compatable target with current profile '$0'") -DIAGNOSTIC(41012, Warning, profileImplicitlyUpgraded, "user set `profile` had an implicit upgrade applied to it, atoms added: '$0'") -DIAGNOSTIC(41012, Error, profileImplicitlyUpgradedRestrictive, "user set `profile` had an implicit upgrade applied to it, atoms added: '$0'") +DIAGNOSTIC(41012, Warning, profileImplicitlyUpgraded, "entry point '$0' uses additional capabilities that are not part of the specified profile '$1'. The profile setting is automatically updated to include these capabilities: '$2'") +DIAGNOSTIC(41012, Error, profileImplicitlyUpgradedRestrictive, "entry point '$0' uses capabilities that are not part of the specified profile '$1'. Missing capabilities are: '$2'") DIAGNOSTIC(41015, Warning, usingUninitializedOut, "use of uninitialized out parameter '$0'") DIAGNOSTIC(41016, Warning, usingUninitializedVariable, "use of uninitialized variable '$0'") DIAGNOSTIC(41017, Warning, usingUninitializedGlobalVariable, "use of uninitialized global variable '$0'") diff --git a/tests/language-feature/capability/capability7.slang b/tests/language-feature/capability/capability7.slang new file mode 100644 index 0000000000..21f3d68e41 --- /dev/null +++ b/tests/language-feature/capability/capability7.slang @@ -0,0 +1,52 @@ +//TEST:SIMPLE(filecheck=CHECK): -target glsl -entry computeMain -stage compute -profile sm_5_0 +//TEST:SIMPLE(filecheck=CHECK_IGNORE_CAPS): -target glsl -emit-spirv-directly -entry computeMain -stage compute -profile sm_5_0 -ignore-capabilities + +// Test that we diagnose simplified capabilities +// CHECK_IGNORE_CAPS-NOT: error 36104 +// CHECK-NOT: sm_4_0 +// CHECK-NOT: sm_5_0 +// CHECK-NOT: sm_5_1 +// CHECK: error 36104: 'processDataBad' uses undeclared capability 'sm_6_0' +// CHECK: capability7.slang(28): note: see using of 'processDataBadNested' +// CHECK: capability7.slang(20): note: see definition of 'processDataBadNested' + + +[require(glsl_hlsl_metal_spirv)] +void processDataGood() +{ +} + +[require(hlsl, sm_6_0)] +void processDataBadNested() +{ + AllMemoryBarrier(); +} + +[require(hlsl)] +void processDataBad() +{ + processDataBadNested(); +} + +void myNestedNestedSafeCall() +{ + processDataGood(); +} + +void myNestedNestedBadCall() +{ + processDataGood(); + processDataBad(); +} + +void myNestedCall() +{ + myNestedNestedSafeCall(); + myNestedNestedBadCall(); +} + +[numthreads(1,1,1)] +void computeMain() +{ + myNestedCall(); +} diff --git a/tests/language-feature/capability/capabilitySimplification1.slang b/tests/language-feature/capability/capabilitySimplification1.slang new file mode 100644 index 0000000000..b694673e98 --- /dev/null +++ b/tests/language-feature/capability/capabilitySimplification1.slang @@ -0,0 +1,34 @@ +//TEST:SIMPLE(filecheck=CHECK): -target glsl -entry computeMain -stage compute -profile sm_5_0 +//TEST:SIMPLE(filecheck=CHECK_IGNORE_CAPS): -target glsl -emit-spirv-directly -entry computeMain -stage compute -profile sm_5_0 -ignore-capabilities + + +// CHECK_IGNORE_CAPS-NOT: error 36107 + +// CHECK: error 36107 +// CHECK-SAME: entrypoint 'computeMain' does not support compilation target 'glsl' with stage 'compute' +// CHECK: capabilitySimplification1.slang(21): note: see using of 'WaveMultiPrefixProduct' +// CHECK-NOT: see using of 'WaveMultiPrefixProduct' +// CHECK: {{.*}}.meta.slang({{.*}}): note: see definition of 'WaveMultiPrefixProduct' +// CHECK: {{.*}}.meta.slang({{.*}}): note: see declaration of 'require' + +void nestedSafeCall() +{ + AllMemoryBarrier(); +} + +void nestedBadCall() +{ + WaveMultiPrefixProduct(1, 0); +} + +void nestedCall() +{ + nestedSafeCall(); + nestedBadCall(); +} + +[numthreads(1,1,1)] +void computeMain() +{ + nestedCall(); +} diff --git a/tests/language-feature/capability/capabilitySimplification2.slang b/tests/language-feature/capability/capabilitySimplification2.slang new file mode 100644 index 0000000000..8d96884ce0 --- /dev/null +++ b/tests/language-feature/capability/capabilitySimplification2.slang @@ -0,0 +1,27 @@ +//TEST:SIMPLE(filecheck=SPIRV): -target spirv -emit-spirv-directly -entry computeMain -stage compute -profile sm_5_0 +//TEST:SIMPLE(filecheck=GLSL): -target glsl -entry computeMain -stage compute -profile sm_5_0 +//TEST:SIMPLE(filecheck=HLSL): -target hlsl -entry computeMain -stage compute -profile sm_5_0 +//TEST:SIMPLE(filecheck=CHECK_IGNORE_CAPS): -target spirv -emit-spirv-directly -entry computeMain -stage compute -profile sm_5_0 -ignore-capabilities + + +// CHECK_IGNORE_CAPS-NOT: warning 41012 + +// SPIRV: warning 41012 +// SPIRV-NOT: spirv_1_2 +// SPIRV-NOT: spirv_1_3 +// SPIRV-SAME: spvGroupNonUniformBallot + +// GLSL: warning 41012 +// GLSL-NOT: GLSL_400 +// GLSL-NOT: GLSL_430 +// GLSL-SAME: GL_KHR_shader_subgroup_ballot + +// HLSL: warning 41012 +// HLSL-NOT: sm_5_1 +// HLSL-SAME: sm_6_0 + +[require(sm_6_0)] +[numthreads(1,1,1)] +void computeMain() +{ +} diff --git a/tests/language-feature/capability/capabilitySimplification3.slang b/tests/language-feature/capability/capabilitySimplification3.slang new file mode 100644 index 0000000000..faf161d15c --- /dev/null +++ b/tests/language-feature/capability/capabilitySimplification3.slang @@ -0,0 +1,17 @@ +//TEST:SIMPLE(filecheck=CHECK): -target glsl -entry computeMain -stage compute -profile sm_5_0 +//TEST:SIMPLE(filecheck=CHECK_IGNORE_CAPS): -target glsl -emit-spirv-directly -entry computeMain -stage compute -profile sm_5_0 -ignore-capabilities + + +// CHECK_IGNORE_CAPS-NOT: error 36107 + +// CHECK: error 36107: entrypoint 'computeMain' does not support compilation target 'glsl' with stage 'compute' +// CHECK: capabilitySimplification3.slang(16): note: see using of 'WaveMultiPrefixProduct' +// CHECK-NOT: see using of 'WaveMultiPrefixProduct' +// CHECK: {{.*}}.meta.slang({{.*}}): note: see definition of 'WaveMultiPrefixProduct' +// CHECK: {{.*}}.meta.slang({{.*}}): note: see declaration of 'require' + +[numthreads(1,1,1)] +void computeMain() +{ + WaveMultiPrefixProduct(1, 0); +}