Skip to content

Commit 56659cd

Browse files
damyanpCopilot
andcommitted
[PIX] Select callable and node shader debug invocations
Instrument callable shaders, selecting invocations by dispatchRaysIndex, and select node shader invocations according to launch type, reporting whether the selection is exact or approximate. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 40dc9de3-617e-4caf-ab0d-fba0a033ed93
1 parent c8b1e52 commit 56659cd

6 files changed

Lines changed: 437 additions & 4 deletions

File tree

‎lib/DxilPIXPasses/DxilDebugInstrumentation.cpp‎

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,10 @@ class DxilDebugInstrumentation : public ModulePass {
336336
DXIL::ShaderKind shaderKind);
337337
Value *addPixelShaderProlog(BuilderContext &BC, SystemValueIndices SVIndices);
338338
Value *addGeometryShaderProlog(BuilderContext &BC);
339+
Value *addThreadIdComparisonProlog(BuilderContext &BC,
340+
DXIL::OpCode threadIdOpCode);
339341
Value *addDispatchedShaderProlog(BuilderContext &BC);
342+
Value *addNodeShaderProlog(BuilderContext &BC);
340343
Value *addRaygenShaderProlog(BuilderContext &BC);
341344
Value *addVertexShaderProlog(BuilderContext &BC,
342345
SystemValueIndices SVIndices);
@@ -392,6 +395,7 @@ static bool IsInstrumentableShaderKind(DXIL::ShaderKind shaderKind) {
392395
case DXIL::ShaderKind::AnyHit:
393396
case DXIL::ShaderKind::ClosestHit:
394397
case DXIL::ShaderKind::Miss:
398+
case DXIL::ShaderKind::Callable:
395399
case DXIL::ShaderKind::Node:
396400
return true;
397401
default:
@@ -512,6 +516,7 @@ DxilDebugInstrumentation::addRequiredSystemValues(BuilderContext &BC,
512516
case DXIL::ShaderKind::AnyHit:
513517
case DXIL::ShaderKind::ClosestHit:
514518
case DXIL::ShaderKind::Miss:
519+
case DXIL::ShaderKind::Callable:
515520
case DXIL::ShaderKind::Node:
516521
// Dispatch* thread Id is not in the input signature
517522
break;
@@ -563,14 +568,15 @@ DxilDebugInstrumentation::addRequiredSystemValues(BuilderContext &BC,
563568
return SVIndices;
564569
}
565570

566-
Value *DxilDebugInstrumentation::addDispatchedShaderProlog(BuilderContext &BC) {
571+
Value *DxilDebugInstrumentation::addThreadIdComparisonProlog(
572+
BuilderContext &BC, DXIL::OpCode threadIdOpCode) {
567573
Constant *Zero32Arg = BC.HlslOP->GetU32Const(0);
568574
Constant *One32Arg = BC.HlslOP->GetU32Const(1);
569575
Constant *Two32Arg = BC.HlslOP->GetU32Const(2);
570576

571577
auto ThreadIdFunc =
572-
BC.HlslOP->GetOpFunc(DXIL::OpCode::ThreadId, Type::getInt32Ty(BC.Ctx));
573-
Constant *Opcode = BC.HlslOP->GetU32Const((unsigned)DXIL::OpCode::ThreadId);
578+
BC.HlslOP->GetOpFunc(threadIdOpCode, Type::getInt32Ty(BC.Ctx));
579+
Constant *Opcode = BC.HlslOP->GetU32Const((unsigned)threadIdOpCode);
574580
auto ThreadIdX =
575581
BC.Builder.CreateCall(ThreadIdFunc, {Opcode, Zero32Arg}, "ThreadIdX");
576582
auto ThreadIdY =
@@ -598,6 +604,62 @@ Value *DxilDebugInstrumentation::addDispatchedShaderProlog(BuilderContext &BC) {
598604
return CompareAll;
599605
}
600606

607+
Value *DxilDebugInstrumentation::addDispatchedShaderProlog(BuilderContext &BC) {
608+
return addThreadIdComparisonProlog(BC, DXIL::OpCode::ThreadId);
609+
}
610+
611+
// The identity of a node shader invocation depends on how the node launches,
612+
// and the three launch types offer different system values. The validator
613+
// allows SV_DispatchThreadID and SV_GroupID only to a broadcasting node, and
614+
// SV_GroupThreadID and SV_GroupIndex only to a broadcasting or a coalescing
615+
// node. A thread launch node gets none of them.
616+
//
617+
// - A broadcasting node runs over a grid, so SV_DispatchThreadID names an
618+
// invocation exactly as it does for a compute shader.
619+
// - A coalescing node has only the position of a thread within its group.
620+
// That narrows the selection to the group size instead of to a single
621+
// invocation.
622+
// - A thread launch node has no thread identity at all, so every invocation
623+
// stays of interest.
624+
//
625+
// The pass report names which of the three applies, so the caller presents the
626+
// selection as exact or approximate.
627+
Value *DxilDebugInstrumentation::addNodeShaderProlog(BuilderContext &BC) {
628+
llvm::Function *function = BC.Builder.GetInsertBlock()->getParent();
629+
630+
if (!BC.DM.HasDxilFunctionProps(function)) {
631+
*OSOverride << "NodeInvocationSelection:None\n";
632+
return BC.HlslOP->GetI1Const(1);
633+
}
634+
635+
hlsl::DxilFunctionProps const &props = BC.DM.GetDxilFunctionProps(function);
636+
637+
switch (props.Node.LaunchType) {
638+
case DXIL::NodeLaunchType::Broadcasting:
639+
*OSOverride << "NodeInvocationSelection:DispatchThreadId\n";
640+
return addThreadIdComparisonProlog(BC, DXIL::OpCode::ThreadId);
641+
642+
case DXIL::NodeLaunchType::Coalescing:
643+
// The requested coordinates only mean something as a position within the
644+
// group. A coordinate outside the declared group size matches no
645+
// invocation, and a criterion that nothing satisfies leaves the debugger
646+
// with no trace at all. Select every invocation in that case instead.
647+
if (m_Parameters.ComputeShader.ThreadIdX < props.numThreads[0] &&
648+
m_Parameters.ComputeShader.ThreadIdY < props.numThreads[1] &&
649+
m_Parameters.ComputeShader.ThreadIdZ < props.numThreads[2]) {
650+
*OSOverride << "NodeInvocationSelection:GroupThreadId\n";
651+
return addThreadIdComparisonProlog(BC, DXIL::OpCode::ThreadIdInGroup);
652+
}
653+
*OSOverride << "NodeInvocationSelection:None\n";
654+
return BC.HlslOP->GetI1Const(1);
655+
656+
case DXIL::NodeLaunchType::Thread:
657+
default:
658+
*OSOverride << "NodeInvocationSelection:None\n";
659+
return BC.HlslOP->GetI1Const(1);
660+
}
661+
}
662+
601663
Value *DxilDebugInstrumentation::addRaygenShaderProlog(BuilderContext &BC) {
602664
auto DispatchRaysIndexOpFunc = BC.HlslOP->GetOpFunc(
603665
DXIL::OpCode::DispatchRaysIndex, Type::getInt32Ty(BC.Ctx));
@@ -819,10 +881,16 @@ void DxilDebugInstrumentation::addInvocationSelectionProlog(
819881
case DXIL::ShaderKind::Intersection:
820882
case DXIL::ShaderKind::AnyHit:
821883
case DXIL::ShaderKind::Miss:
884+
case DXIL::ShaderKind::Callable:
885+
// A callable shader has neither a ray nor a thread of its own, but it runs
886+
// as part of the dispatch that calls it, so DispatchRaysIndex names the
887+
// invocation that leads to it. PIX selects a raygen invocation on that same
888+
// value, so a callable that the selected raygen thread invokes is selected
889+
// here too.
822890
ParameterTestResult = addRaygenShaderProlog(BC);
823891
break;
824892
case DXIL::ShaderKind::Node:
825-
ParameterTestResult = BC.HlslOP->GetI1Const(1);
893+
ParameterTestResult = addNodeShaderProlog(BC);
826894
break;
827895
case DXIL::ShaderKind::Compute:
828896
case DXIL::ShaderKind::Amplification:
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// RUN: %dxc -T lib_6_3 %s | %opt -S -dxil-annotate-with-virtual-regs -hlsl-dxil-debug-instrumentation,parameter0=1,parameter1=2,parameter2=3 -hlsl-dxilemit | %FileCheck %s
2+
3+
// A callable shader is a valid CallShader() target, so PIX must be able to step
4+
// into it. The pass numbers its instructions and advertises them to PIX, so it
5+
// must instrument it as well.
6+
//
7+
// A callable shader has no ray and no thread of its own, but
8+
// dx.op.dispatchRaysIndex is legal in it, and reports the index of the ray
9+
// generation invocation that is responsible for the call. PIX selects a raygen,
10+
// any-hit, closest-hit or miss invocation on that same identity, so a callable
11+
// shader uses it too.
12+
13+
// The callable shader is the only shader in the module, so a Block# line means
14+
// that it is instrumented.
15+
// CHECK: Block#
16+
17+
// CHECK: %RayX = call i32 @dx.op.dispatchRaysIndex.i32(i32 145, i8 0)
18+
// CHECK: %RayY = call i32 @dx.op.dispatchRaysIndex.i32(i32 145, i8 1)
19+
// CHECK: %RayZ = call i32 @dx.op.dispatchRaysIndex.i32(i32 145, i8 2)
20+
// CHECK: %CompareToThreadIdX = icmp eq i32 %RayX, 1
21+
// CHECK: %CompareToThreadIdY = icmp eq i32 %RayY, 2
22+
// CHECK: %CompareToThreadIdZ = icmp eq i32 %RayZ, 3
23+
// CHECK: %CompareAll = and i1 %CompareXAndY, %CompareToThreadIdZ
24+
// CHECK: br i1 %CompareAll, label %PIXInterestingBlock, label %PIXNonInterestingBlock
25+
26+
RWStructuredBuffer<float> Output : register(u0);
27+
28+
struct CallableParameters
29+
{
30+
float value;
31+
};
32+
33+
[shader("callable")]
34+
void MyCallable(inout CallableParameters parameters)
35+
{
36+
parameters.value = parameters.value * 2.f;
37+
Output[0] = parameters.value;
38+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// RUN: %dxc -T lib_6_8 %s | %opt -S -dxil-annotate-with-virtual-regs -hlsl-dxil-debug-instrumentation,parameter0=10,parameter1=20,parameter2=30 -hlsl-dxilemit | %FileCheck %s
2+
3+
// A broadcasting node runs over a grid, so SV_DispatchThreadID names an
4+
// invocation exactly as it does for a compute shader, and the debugger selects
5+
// the one that the user asks for. A constant-true criterion instead makes every
6+
// invocation believe that it is the selected one, and the debugger shows
7+
// whichever one reaches the UAV first.
8+
9+
// CHECK: NodeInvocationSelection:DispatchThreadId
10+
11+
// CHECK: %ThreadIdX = call i32 @dx.op.threadId.i32(i32 93, i32 0)
12+
// CHECK: %ThreadIdY = call i32 @dx.op.threadId.i32(i32 93, i32 1)
13+
// CHECK: %ThreadIdZ = call i32 @dx.op.threadId.i32(i32 93, i32 2)
14+
// CHECK: %CompareToThreadIdX = icmp eq i32 %ThreadIdX, 10
15+
// CHECK: %CompareToThreadIdY = icmp eq i32 %ThreadIdY, 20
16+
// CHECK: %CompareToThreadIdZ = icmp eq i32 %ThreadIdZ, 30
17+
// CHECK: %CompareXAndY = and i1 %CompareToThreadIdX, %CompareToThreadIdY
18+
// CHECK: %CompareAll = and i1 %CompareXAndY, %CompareToThreadIdZ
19+
// CHECK: br i1 %CompareAll, label %PIXInterestingBlock, label %PIXNonInterestingBlock
20+
21+
RWStructuredBuffer<uint> Output : register(u0);
22+
23+
struct Record
24+
{
25+
uint value;
26+
};
27+
28+
[Shader("node")]
29+
[NodeLaunch("broadcasting")]
30+
[NodeDispatchGrid(2, 1, 1)]
31+
[NumThreads(4, 2, 1)]
32+
void BroadcastingNode(DispatchNodeInputRecord<Record> input)
33+
{
34+
Output[0] = input.Get().value;
35+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// RUN: %dxc -T lib_6_8 %s | %opt -S -dxil-annotate-with-virtual-regs -hlsl-dxil-debug-instrumentation,parameter0=3,parameter1=1,parameter2=0 -hlsl-dxilemit | %FileCheck %s
2+
3+
// A coalescing node has no dispatch grid, so dx.op.threadId is not legal for it.
4+
// ValidateDxilOperationCallInProfile in DxilValidation.cpp permits ThreadId and
5+
// GroupId for a broadcasting launch only. The thread group ID is legal, so the
6+
// debugger discriminates invocations within a group by SV_GroupThreadID.
7+
8+
// CHECK: NodeInvocationSelection:GroupThreadId
9+
10+
// CHECK: %ThreadIdX = call i32 @dx.op.threadIdInGroup.i32(i32 95, i32 0)
11+
// CHECK: %ThreadIdY = call i32 @dx.op.threadIdInGroup.i32(i32 95, i32 1)
12+
// CHECK: %ThreadIdZ = call i32 @dx.op.threadIdInGroup.i32(i32 95, i32 2)
13+
// CHECK: %CompareToThreadIdX = icmp eq i32 %ThreadIdX, 3
14+
// CHECK: %CompareToThreadIdY = icmp eq i32 %ThreadIdY, 1
15+
// CHECK: %CompareToThreadIdZ = icmp eq i32 %ThreadIdZ, 0
16+
// CHECK: %CompareAll = and i1 %CompareXAndY, %CompareToThreadIdZ
17+
// CHECK: br i1 %CompareAll, label %PIXInterestingBlock, label %PIXNonInterestingBlock
18+
19+
// The requested thread must lie inside the declared thread group, or the pass
20+
// discriminates nothing. The parameters above lie inside [NumThreads(4, 2, 1)].
21+
// CHECK-NOT: NodeInvocationSelection:None
22+
23+
RWStructuredBuffer<uint> Output : register(u0);
24+
25+
struct Record
26+
{
27+
uint value;
28+
};
29+
30+
[Shader("node")]
31+
[NodeLaunch("coalescing")]
32+
[NumThreads(4, 2, 1)]
33+
void CoalescingNode(GroupNodeInputRecords<Record> input)
34+
{
35+
Output[0] = input.Get(0).value;
36+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// RUN: %dxc -T lib_6_8 %s | %opt -S -dxil-annotate-with-virtual-regs -hlsl-dxil-debug-instrumentation,parameter0=1,parameter1=2,parameter2=3 -hlsl-dxilemit | %FileCheck %s
2+
3+
// A thread launch node has neither a dispatch grid nor a thread group, so none
4+
// of the thread-identity intrinsics are legal for it.
5+
// ValidateDxilOperationCallInProfile in DxilValidation.cpp rejects ThreadId,
6+
// GroupId, ThreadIdInGroup and FlattenedThreadIdInGroup for a thread launch
7+
// node. Nothing in the shader tells one invocation from another, so the pass
8+
// keeps the select-everything criterion. This test pins that decision, so that
9+
// a change cannot emit an intrinsic that the validator rejects.
10+
11+
// CHECK: NodeInvocationSelection:None
12+
13+
// CHECK-NOT: @dx.op.threadId.i32
14+
// CHECK-NOT: @dx.op.threadIdInGroup.i32
15+
// CHECK-NOT: @dx.op.flattenedThreadIdInGroup.i32
16+
// CHECK: br i1 true, label %PIXInterestingBlock, label %PIXNonInterestingBlock
17+
18+
RWStructuredBuffer<uint> Output : register(u0);
19+
20+
struct Record
21+
{
22+
uint value;
23+
};
24+
25+
[Shader("node")]
26+
[NodeLaunch("thread")]
27+
void ThreadLaunchNode(ThreadNodeInputRecord<Record> input)
28+
{
29+
Output[0] = input.Get().value;
30+
}

0 commit comments

Comments
 (0)