Skip to content

Commit 90b3817

Browse files
authored
Make capability diagnostic message more friendly. (#6474)
* Make capability diagnostic message more friendly. * Fix. * Fix. * Fix. * Fix test. * Update expected fail setting for aarch64/linux * Fix.
1 parent 6cf15f4 commit 90b3817

9 files changed

+162
-66
lines changed

.github/workflows/ci.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ jobs:
166166
-use-test-server \
167167
-category ${{ matrix.test-category }} \
168168
-api all-dx12 \
169-
-expected-failure-list tests/expected-failure-github.txt
169+
-expected-failure-list tests/expected-failure-github.txt \
170+
-expected-failure-list tests/expected-failure-record-replay-tests.txt
170171
else
171172
"$bin_dir/slang-test" \
172173
-use-test-server \

source/slang/slang-ast-base.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -741,9 +741,9 @@ class ModifiableSyntaxNode : public SyntaxNode
741741
}
742742
};
743743

744-
struct DeclReferenceWithLoc
744+
struct ProvenenceNodeWithLoc
745745
{
746-
Decl* referencedDecl;
746+
NodeBase* referencedNode;
747747
SourceLoc referenceLoc;
748748
};
749749

@@ -789,7 +789,7 @@ class Decl : public DeclBase
789789
bool isChildOf(Decl* other) const;
790790

791791
// Track the decl reference that caused the requirement of a capability atom.
792-
SLANG_UNREFLECTED List<DeclReferenceWithLoc> capabilityRequirementProvenance;
792+
SLANG_UNREFLECTED List<ProvenenceNodeWithLoc> capabilityRequirementProvenance;
793793

794794
SLANG_UNREFLECTED bool hiddenFromLookup = false;
795795

source/slang/slang-check-decl.cpp

+111-56
Original file line numberDiff line numberDiff line change
@@ -12744,11 +12744,11 @@ static void _propagateRequirement(
1274412744
// if stmt inside parent, set the provenance tracker to the calling function
1274512745
if (!decl)
1274612746
decl = visitor->getParentFuncOfVisitor();
12747-
if (referencedDecl && decl)
12747+
if (referencedNode && decl)
1274812748
{
1274912749
// Here we store a childDecl that added/removed capabilities from a parentDecl
1275012750
decl->capabilityRequirementProvenance.add(
12751-
DeclReferenceWithLoc{referencedDecl, referenceLoc});
12751+
ProvenenceNodeWithLoc{referencedNode, referenceLoc});
1275212752
}
1275312753
};
1275412754

@@ -12977,15 +12977,19 @@ CapabilitySet SemanticsDeclCapabilityVisitor::getDeclaredCapabilitySet(Decl* dec
1297712977
// The requirement for `foo` should be glsl+glsl_ext_1 | spirv.
1297812978
//
1297912979
CapabilitySet declaredCaps;
12980+
CapabilityAtom stageToJoin = CapabilityAtom::Invalid;
1298012981
for (Decl* parent = decl; parent; parent = getParentDecl(parent))
1298112982
{
1298212983
CapabilitySet localDeclaredCaps;
1298312984
bool shouldBreak = false;
1298412985
if (!as<AggTypeDeclBase>(parent) || parent->inferredCapabilityRequirements.isEmpty())
1298512986
{
12986-
for (auto decoration : parent->getModifiersOfType<RequireCapabilityAttribute>())
12987+
for (auto mod : parent->modifiers)
1298712988
{
12988-
localDeclaredCaps.unionWith(decoration->capabilitySet);
12989+
if (auto decoration = as<RequireCapabilityAttribute>(mod))
12990+
localDeclaredCaps.unionWith(decoration->capabilitySet);
12991+
else if (auto entrypoint = as<EntryPointAttribute>(mod))
12992+
stageToJoin = entrypoint->capabilitySet.getTargetStage();
1298912993
}
1299012994
}
1299112995
else
@@ -13001,6 +13005,8 @@ CapabilitySet SemanticsDeclCapabilityVisitor::getDeclaredCapabilitySet(Decl* dec
1300113005
if (shouldBreak)
1300213006
break;
1300313007
}
13008+
if (!declaredCaps.isEmpty() && stageToJoin != CapabilityAtom::Invalid)
13009+
declaredCaps.join(CapabilitySet((CapabilityName)stageToJoin));
1300413010
return declaredCaps;
1300513011
}
1300613012

@@ -13298,63 +13304,83 @@ void diagnoseMissingCapabilityProvenance(
1329813304
Decl* decl,
1329913305
CapabilitySet& setToFind)
1330013306
{
13301-
HashSet<Decl*> checkedDecls;
13302-
DeclReferenceWithLoc declWithRef;
13303-
declWithRef.referencedDecl = decl;
13304-
declWithRef.referenceLoc = (decl) ? decl->loc : SourceLoc();
13307+
HashSet<NodeBase*> checkedDecls;
13308+
ProvenenceNodeWithLoc provNode;
13309+
provNode.referencedNode = decl;
13310+
provNode.referenceLoc = (decl) ? decl->loc : SourceLoc();
1330513311
bool bottomOfProvenanceStack = false;
1330613312
// Find the bottom of the atom provenance stack which fails to contain `setToFind`
13307-
while (!bottomOfProvenanceStack && declWithRef.referencedDecl)
13313+
while (!bottomOfProvenanceStack && provNode.referencedNode)
1330813314
{
1330913315
bottomOfProvenanceStack = true;
13310-
for (auto& i : declWithRef.referencedDecl->capabilityRequirementProvenance)
13316+
if (auto referencedDecl = as<Decl>(provNode.referencedNode))
1331113317
{
13312-
if (checkedDecls.contains(i.referencedDecl))
13313-
continue;
13314-
checkedDecls.add(i.referencedDecl);
13315-
13316-
if (!i.referencedDecl->inferredCapabilityRequirements.implies(setToFind))
13318+
for (auto& i : referencedDecl->capabilityRequirementProvenance)
1331713319
{
13318-
// We found a source of the incompatible capability, follow this
13319-
// element inside the provenance stack until we are at the bottom
13320-
declWithRef = i;
13321-
bottomOfProvenanceStack = false;
13322-
break;
13320+
if (checkedDecls.contains(i.referencedNode))
13321+
continue;
13322+
checkedDecls.add(i.referencedNode);
13323+
auto innerReferencedDecl = as<Decl>(i.referencedNode);
13324+
if (!innerReferencedDecl ||
13325+
!innerReferencedDecl->inferredCapabilityRequirements.implies(setToFind))
13326+
{
13327+
// We found a source of the incompatible capability, follow this
13328+
// element inside the provenance stack until we are at the bottom
13329+
provNode = i;
13330+
bottomOfProvenanceStack = false;
13331+
break;
13332+
}
1332313333
}
1332413334
}
13335+
else
13336+
{
13337+
bottomOfProvenanceStack = true;
13338+
}
1332513339
}
1332613340

13327-
if (!declWithRef.referencedDecl)
13341+
if (!provNode.referencedNode)
1332813342
return;
1332913343

13330-
// Diagnose the use-site
13331-
maybeDiagnose(
13332-
sink,
13333-
optionSet,
13334-
DiagnosticCategory::Capability,
13335-
declWithRef.referenceLoc,
13336-
Diagnostics::seeUsingOf,
13337-
declWithRef.referencedDecl);
13338-
// Diagnose the definition as the problem
13339-
maybeDiagnose(
13340-
sink,
13341-
optionSet,
13342-
DiagnosticCategory::Capability,
13343-
declWithRef.referencedDecl->loc,
13344-
Diagnostics::seeDefinitionOf,
13345-
declWithRef.referencedDecl);
13346-
13347-
// If we find a 'require' modifier, this is contributing to the overall capability
13348-
// incompatibility. We should hint to the user that this declaration is problematic.
13349-
if (auto requireCapabilityAttribute =
13350-
declWithRef.referencedDecl->findModifier<RequireCapabilityAttribute>())
13344+
if (auto referencedDecl = as<Decl>(provNode.referencedNode))
13345+
{
13346+
// Diagnose the use-site
1335113347
maybeDiagnose(
1335213348
sink,
1335313349
optionSet,
1335413350
DiagnosticCategory::Capability,
13355-
requireCapabilityAttribute->loc,
13356-
Diagnostics::seeDeclarationOf,
13357-
requireCapabilityAttribute);
13351+
provNode.referenceLoc,
13352+
Diagnostics::seeUsingOf,
13353+
referencedDecl);
13354+
// Diagnose the definition as the problem
13355+
maybeDiagnose(
13356+
sink,
13357+
optionSet,
13358+
DiagnosticCategory::Capability,
13359+
referencedDecl->loc,
13360+
Diagnostics::seeDefinitionOf,
13361+
referencedDecl);
13362+
// If we find a 'require' modifier, this is contributing to the overall capability
13363+
// incompatibility. We should hint to the user that this declaration is problematic.
13364+
if (auto requireCapabilityAttribute =
13365+
referencedDecl->findModifier<RequireCapabilityAttribute>())
13366+
maybeDiagnose(
13367+
sink,
13368+
optionSet,
13369+
DiagnosticCategory::Capability,
13370+
requireCapabilityAttribute->loc,
13371+
Diagnostics::seeDeclarationOf,
13372+
requireCapabilityAttribute);
13373+
}
13374+
else
13375+
{
13376+
maybeDiagnose(
13377+
sink,
13378+
optionSet,
13379+
DiagnosticCategory::Capability,
13380+
provNode.referenceLoc,
13381+
Diagnostics::seeUsingOf,
13382+
provNode.referencedNode->astNodeType);
13383+
}
1335813384
}
1335913385

1336013386
void diagnoseCapabilityProvenance(
@@ -13372,16 +13398,29 @@ void diagnoseCapabilityProvenance(
1337213398
printedDecls.add(declToPrint);
1337313399
for (auto& provenance : declToPrint->capabilityRequirementProvenance)
1337413400
{
13375-
if (!provenance.referencedDecl->inferredCapabilityRequirements.implies(atomToFind))
13401+
auto referencedDecl = as<Decl>(provenance.referencedNode);
13402+
if (!referencedDecl)
13403+
{
13404+
maybeDiagnose(
13405+
sink,
13406+
optionSet,
13407+
DiagnosticCategory::Capability,
13408+
provenance.referenceLoc,
13409+
Diagnostics::seeUsingOf,
13410+
provenance.referencedNode->astNodeType);
13411+
break;
13412+
}
13413+
13414+
if (!referencedDecl->inferredCapabilityRequirements.implies(atomToFind))
1337613415
continue;
1337713416
maybeDiagnose(
1337813417
sink,
1337913418
optionSet,
1338013419
DiagnosticCategory::Capability,
1338113420
provenance.referenceLoc,
1338213421
Diagnostics::seeUsingOf,
13383-
provenance.referencedDecl);
13384-
declToPrint = provenance.referencedDecl;
13422+
referencedDecl);
13423+
declToPrint = referencedDecl;
1338513424
if (printedDecls.contains(declToPrint))
1338613425
break;
1338713426
if (declToPrint->findModifier<RequireCapabilityAttribute>())
@@ -13487,14 +13526,30 @@ void SemanticsDeclCapabilityVisitor::diagnoseUndeclaredCapability(
1348713526
for (auto i : simplifiedFailedAtomsSet)
1348813527
{
1348913528
CapabilityAtom formattedAtom = asAtom(i);
13490-
maybeDiagnose(
13491-
getSink(),
13492-
this->getOptionSet(),
13493-
DiagnosticCategory::Capability,
13494-
decl->loc,
13495-
diagnosticInfo,
13496-
decl,
13497-
formattedAtom);
13529+
CapabilityName canonicalName;
13530+
if (isStageAtom((CapabilityName)formattedAtom, canonicalName))
13531+
{
13532+
// Provide a more friendly message if atom is a stage.
13533+
maybeDiagnose(
13534+
getSink(),
13535+
this->getOptionSet(),
13536+
DiagnosticCategory::Capability,
13537+
decl->loc,
13538+
Diagnostics::declHasDependenciesNotCompatibleOnStage,
13539+
decl,
13540+
formattedAtom);
13541+
}
13542+
else
13543+
{
13544+
maybeDiagnose(
13545+
getSink(),
13546+
this->getOptionSet(),
13547+
DiagnosticCategory::Capability,
13548+
decl->loc,
13549+
diagnosticInfo,
13550+
decl,
13551+
formattedAtom);
13552+
}
1349813553
// Print provenances.
1349913554
diagnoseCapabilityProvenance(
1350013555
this->getOptionSet(),

source/slang/slang-diagnostic-defs.h

+6-2
Original file line numberDiff line numberDiff line change
@@ -987,7 +987,7 @@ DIAGNOSTIC(
987987
36107,
988988
Error,
989989
entryPointUsesUnavailableCapability,
990-
"entrypoint '$0' does not support compilation target '$1' with stage '$2'")
990+
"entrypoint '$0' uses features that are not available in '$2' stage for '$1' target.")
991991
DIAGNOSTIC(
992992
36108,
993993
Error,
@@ -1025,7 +1025,11 @@ DIAGNOSTIC(
10251025
Error,
10261026
capabilityHasMultipleStages,
10271027
"Capability '$0' is targeting stages '$1', only allowed to use 1 unique stage here.")
1028-
1028+
DIAGNOSTIC(
1029+
36117,
1030+
Error,
1031+
declHasDependenciesNotCompatibleOnStage,
1032+
"'$0' uses features that are not available in '$1' stage.")
10291033

10301034
// Attributes
10311035
DIAGNOSTIC(31000, Warning, unknownAttributeName, "unknown attribute '$0'")

source/slang/slang-syntax.cpp

+13-1
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,20 @@ void printDiagnosticArg(StringBuilder& sb, ASTNodeType nodeType)
223223
case ASTNodeType::RequireCapabilityDecl:
224224
sb << "__require_capability";
225225
break;
226+
case ASTNodeType::DiscardStmt:
227+
sb << "discard";
228+
break;
226229
default:
227-
sb << "decl";
230+
if (ASTClassInfo::getInfo(nodeType)->isDerivedFrom((uint32_t)ASTNodeType::Expr))
231+
sb << "expression";
232+
else if (ASTClassInfo::getInfo(nodeType)->isDerivedFrom((uint32_t)ASTNodeType::Stmt))
233+
sb << "statement";
234+
else if (ASTClassInfo::getInfo(nodeType)->isDerivedFrom((uint32_t)ASTNodeType::Decl))
235+
sb << "decl";
236+
else if (ASTClassInfo::getInfo(nodeType)->isDerivedFrom((uint32_t)ASTNodeType::Val))
237+
sb << "val";
238+
else
239+
sb << "node";
228240
break;
229241
}
230242
}

tests/diagnostics/discard-in-rt.slang

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//TEST:SIMPLE(filecheck=CHECK): -target spirv
2+
3+
// CHECK: 'closestHit' uses features that are not available in 'closesthit' stage.
4+
// CHECK: see using of 'discard'
5+
6+
struct PrimaryRayPayload{}
7+
[require(spvRayTracingKHR)]
8+
[shader("closesthit")]
9+
void closestHit(
10+
inout PrimaryRayPayload rayData : SV_RayPayload,
11+
BuiltInTriangleIntersectionAttributes attribs : SV_IntersectionAttributes)
12+
{
13+
discard;
14+
}
15+
16+
// CHECK: 'closestHit1' uses features that are not available in 'closesthit' stage
17+
// CHECK: see using of 'discard'
18+
[shader("closesthit")]
19+
void closestHit1(
20+
inout PrimaryRayPayload rayData : SV_RayPayload,
21+
BuiltInTriangleIntersectionAttributes attribs : SV_IntersectionAttributes)
22+
{
23+
discard;
24+
}

tests/language-feature/capability/capabilitySimplification1.slang

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// CHECK_IGNORE_CAPS-NOT: error 36107
66

77
// CHECK: error 36107
8-
// CHECK-SAME: entrypoint 'computeMain' does not support compilation target 'glsl' with stage 'compute'
8+
// CHECK-SAME: entrypoint 'computeMain' uses features that are not available in 'compute' stage for 'glsl' target.
99
// CHECK: capabilitySimplification1.slang(21): note: see using of 'WaveMultiPrefixCountBits'
1010
// CHECK-NOT: see using of 'WaveMultiPrefixCountBits'
1111
// CHECK: {{.*}}.meta.slang({{.*}}): note: see definition of 'WaveMultiPrefixCountBits'

tests/language-feature/capability/capabilitySimplification3.slang

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
// CHECK_IGNORE_CAPS-NOT: error 36107
66

7-
// CHECK: error 36107: entrypoint 'computeMain' does not support compilation target 'glsl' with stage 'compute'
7+
// CHECK: error 36107: entrypoint 'computeMain' uses features that are not available in 'compute' stage for 'glsl' target.
88
// CHECK: capabilitySimplification3.slang(16): note: see using of 'WaveMultiPrefixCountBits'
99
// CHECK-NOT: see using of 'WaveMultiPrefixCountBits'
1010
// CHECK: {{.*}}.meta.slang({{.*}}): note: see definition of 'WaveMultiPrefixCountBits'

tests/language-feature/capability/explicit-shader-stage-2.slang

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//TEST:SIMPLE(filecheck=CHECK): -target hlsl -entry main -allow-glsl -profile sm_5_0
22
//TEST:SIMPLE(filecheck=CHECK_IGNORE_CAPS): -target hlsl -entry main -allow-glsl -profile sm_5_0 -ignore-capabilities
33

4-
//CHECK: error 36107: entrypoint 'main' does not support compilation target 'hlsl' with stage 'fragment'
4+
//CHECK: error 36107: entrypoint 'main' uses features that are not available in 'fragment' stage for 'hlsl' target.
55
//CHECK_IGNORE_CAPS-NOT: error 36100
66
[shader("fragment")]
77
float4 main()

0 commit comments

Comments
 (0)