Skip to content

Commit 12b838f

Browse files
authored
PIX: Cp of "Prevent ray query handle writes to PIX debug UAV" (#6362)
Original PR: #6309 Original commit: 5a31785 AllocateRayQuery returns an int, but it's not really an int: it's a handle to the query. Thus, it's not sensible for the PIX debugging instrumentation to attempt to write it out to the debug-data-UAV. The previous code did an explicit check against the type of the value to be written, but if that value is actually a phi itself, then that check would fail. So now we recursively run through the phi values looking to see if any of its antecedents are a phi, and if so, refuse to send its value to the UAV. (cherry picked from commit 5a31785)
1 parent 7b32b38 commit 12b838f

7 files changed

Lines changed: 161 additions & 67 deletions

File tree

lib/DxilPIXPasses/DxilDbgValueToDbgDeclare.cpp

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include "dxc/DXIL/DxilConstants.h"
1919
#include "dxc/DXIL/DxilModule.h"
20+
#include "dxc/DXIL/DxilOperations.h"
2021
#include "dxc/DXIL/DxilResourceBase.h"
2122
#include "dxc/DxilPIXPasses/DxilPIXPasses.h"
2223
#include "llvm/ADT/STLExtras.h"
@@ -597,23 +598,27 @@ bool DxilDbgValueToDbgDeclare::runOnModule(llvm::Module &M) {
597598

598599
auto &Functions = M.getFunctionList();
599600
for (auto &fn : Functions) {
600-
// #DSLTodo: We probably need to merge the list of variables for each export
601-
// into one set so that WinPIX shader debugging can follow a thread through
602-
// any function within a given module. (Unless PIX chooses to launch a new
603-
// debugging session whenever control passes from one function to another.)
604-
// For now, it's sufficient to treat each exported function as having
605-
// completely separate variables by clearing this member:
601+
llvm::SmallPtrSet<Value *, 16> RayQueryHandles;
602+
PIXPassHelpers::FindRayQueryHandlesForFunction(&fn, RayQueryHandles);
603+
// #DSLTodo: We probably need to merge the list of variables for each
604+
// export into one set so that WinPIX shader debugging can follow a
605+
// thread through any function within a given module. (Unless PIX
606+
// chooses to launch a new debugging session whenever control passes
607+
// from one function to another.) For now, it's sufficient to treat each
608+
// exported function as having completely separate variables by clearing
609+
// this member:
606610
m_Registers.clear();
607-
// Note: they key problem here is variables in common functions called by
608-
// multiple exported functions. The DILocalVariables in the common function
609-
// will be exactly the same objects no matter which export called the common
610-
// function, so the instrumentation here gets a bit confused that the same
611-
// variable is present in two functions and ends up pointing one function
612-
// to allocas in another function. (This is easy to repro: comment out the
613-
// above clear(), and run PixTest::PixStructAnnotation_Lib_DualRaygen.)
614-
// Not sure what the right path forward is: might be that we have to tag
615-
// m_Registers with the exported function, and maybe write out a function
616-
// identifier during debug instrumentation...
611+
// Note: they key problem here is variables in common functions called
612+
// by multiple exported functions. The DILocalVariables in the common
613+
// function will be exactly the same objects no matter which export
614+
// called the common function, so the instrumentation here gets a bit
615+
// confused that the same variable is present in two functions and ends
616+
// up pointing one function to allocas in another function. (This is
617+
// easy to repro: comment out the above clear(), and run
618+
// PixTest::PixStructAnnotation_Lib_DualRaygen.) Not sure what the right
619+
// path forward is: might be that we have to tag m_Registers with the
620+
// exported function, and maybe write out a function identifier during
621+
// debug instrumentation...
617622
auto &blocks = fn.getBasicBlockList();
618623
if (!blocks.empty()) {
619624
for (auto &block : blocks) {
@@ -640,9 +645,8 @@ bool DxilDbgValueToDbgDeclare::runOnModule(llvm::Module &M) {
640645
if (auto *DbgValue =
641646
llvm::dyn_cast<llvm::DbgValueInst>(instruction)) {
642647
llvm::Value *V = DbgValue->getValue();
643-
if (PIXPassHelpers::IsAllocateRayQueryInstruction(V)) {
648+
if (RayQueryHandles.count(V) != 0)
644649
continue;
645-
}
646650
Changed = true;
647651
handleDbgValue(M, DbgValue);
648652
DbgValue->eraseFromParent();
@@ -809,8 +813,8 @@ static llvm::DIType *FindStructMemberTypeAtOffset(llvm::DICompositeType *Ty,
809813
}
810814
}
811815

812-
// Structure resources are expected to fail this (they have no real meaning in
813-
// storage)
816+
// Structure resources are expected to fail this (they have no real
817+
// meaning in storage)
814818
if (SortedMembers.size() == 1) {
815819
switch (SortedMembers.begin()->second->getTag()) {
816820
case llvm::dwarf::DW_TAG_structure_type:
@@ -907,8 +911,9 @@ void DxilDbgValueToDbgDeclare::handleDbgValue(llvm::Module &M,
907911
}
908912

909913
// Members' "base type" is actually the containing aggregate's type.
910-
// To find the actual type of the variable, we must descend the container's
911-
// type hierarchy to find the type at the expected offset/size.
914+
// To find the actual type of the variable, we must descend the
915+
// container's type hierarchy to find the type at the expected
916+
// offset/size.
912917
if (auto *DerivedTy = llvm::dyn_cast<llvm::DIDerivedType>(Ty)) {
913918
const llvm::DITypeIdentifierMap EmptyMap;
914919
switch (DerivedTy->getTag()) {
@@ -961,10 +966,10 @@ void DxilDbgValueToDbgDeclare::handleDbgValue(llvm::Module &M,
961966

962967
auto *Zero = B.getInt32(0);
963968

964-
// Now traverse a list of pairs {Scalar Value, InitialOffset + Offset}.
965-
// InitialOffset is the offset from DbgValue's expression (i.e., the
966-
// offset from the Variable's start), and Offset is the Scalar Value's
967-
// packed offset from DbgValue's value.
969+
// Now traverse a list of pairs {Scalar Value, InitialOffset +
970+
// Offset}. InitialOffset is the offset from DbgValue's expression
971+
// (i.e., the offset from the Variable's start), and Offset is the
972+
// Scalar Value's packed offset from DbgValue's value.
968973
for (const ValueAndOffset &VO : SplitValue(V, InitialOffset, B)) {
969974

970975
OffsetInBits AlignedOffset;
@@ -1022,11 +1027,11 @@ bool DxilDbgValueToDbgDeclare::handleStoreIfDestIsGlobal(
10221027
llvm::dyn_cast<llvm::GetElementPtrInst>(asInstr.Get())) {
10231028
// We are only interested in the case of basic types within an array
10241029
// because the PIX debug instrumentation operates at that level.
1025-
// Aggregate members will have been descended through to produce their
1026-
// own entries in the GlobalStorageMap.
1027-
// Consequently, we're only interested in the GEP's index into the
1028-
// array. Any deeper indexing in the GEP will be for embedded
1029-
// aggregates. The three operands in such a GEP mean:
1030+
// Aggregate members will have been descended through to produce
1031+
// their own entries in the GlobalStorageMap. Consequently, we're
1032+
// only interested in the GEP's index into the array. Any deeper
1033+
// indexing in the GEP will be for embedded aggregates. The three
1034+
// operands in such a GEP mean:
10301035
// 0 = the pointer
10311036
// 1 = dereference the pointer (expected to be constant int zero)
10321037
// 2 = the index into the array
@@ -1035,7 +1040,8 @@ bool DxilDbgValueToDbgDeclare::handleStoreIfDestIsGlobal(
10351040
llvm::dyn_cast<ConstantInt>(asGEP->getOperand(1))
10361041
->getLimitedValue() == 0) {
10371042
// TODO: The case where this index is not a constant int
1038-
// (Needs changes to the allocas generated elsewhere in this pass.)
1043+
// (Needs changes to the allocas generated elsewhere in this
1044+
// pass.)
10391045
if (auto *arrayIndexAsConstInt =
10401046
llvm::dyn_cast<ConstantInt>(asGEP->getOperand(2))) {
10411047
int MemberIndex = arrayIndexAsConstInt->getLimitedValue();
@@ -1120,9 +1126,9 @@ VariableRegisters::VariableRegisters(
11201126
PopulateAllocaMap(Ty);
11211127
m_Offsets.AlignTo(Ty); // For padding.
11221128

1123-
// (min16* types can occupy 16 or 32 bits depending on whether or not they are
1124-
// natively supported. If non-native, the alignment will be 32, but the
1125-
// claimed size will still be 16, hence the "max" here)
1129+
// (min16* types can occupy 16 or 32 bits depending on whether or not they
1130+
// are natively supported. If non-native, the alignment will be 32, but
1131+
// the claimed size will still be 16, hence the "max" here)
11261132
assert(m_Offsets.GetCurrentAlignedOffset() ==
11271133
std::max<uint64_t>(DITypePeelTypeAlias(Ty)->getSizeInBits(),
11281134
DITypePeelTypeAlias(Ty)->getAlignInBits()));

lib/DxilPIXPasses/DxilDebugInstrumentation.cpp

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ using namespace hlsl;
4848
// UAV if the instance is not of interest.
4949
//
5050
// In addition, each half of the UAV is further subdivided: the first quarter is
51-
// the are in which blocks are permitted to start writing their sequence, and
51+
// the area in which blocks are permitted to start writing their sequence, and
5252
// that sequence is constrained to be no longer than the size of the second
5353
// quarter. This allows us to limit writes to the appropriate half of the UAV
54-
// via a single AND at the beginning of the basic block. An additoinal OR
54+
// via a single AND at the beginning of the basic block. An additional OR
5555
// provides the offset, either 0 for threads-of-interest, or UAVSize/2 for
5656
// not-of-interest.
5757
//
@@ -341,8 +341,9 @@ class DxilDebugInstrumentation : public ModulePass {
341341
void reserveDebugEntrySpace(BuilderContext &BC, uint32_t SpaceInDwords);
342342
std::optional<InstructionAndType> addStoreStepDebugEntry(BuilderContext *BC,
343343
StoreInst *Inst);
344-
std::optional<InstructionAndType> addStepDebugEntry(BuilderContext *BC,
345-
Instruction *Inst);
344+
std::optional<InstructionAndType>
345+
addStepDebugEntry(BuilderContext *BC, Instruction *Inst,
346+
llvm::SmallPtrSetImpl<Value *> const &RayQueryHandles);
346347
std::optional<DebugShaderModifierRecordType>
347348
addStepDebugEntryValue(BuilderContext *BC, std::uint32_t InstNum, Value *V,
348349
std::uint32_t ValueOrdinal, Value *ValueOrdinalIndex);
@@ -362,8 +363,9 @@ class DxilDebugInstrumentation : public ModulePass {
362363
uint32_t FirstInstructionOrdinalInBlock;
363364
std::vector<InstructionToInstrument> Instructions;
364365
};
365-
BlockInstrumentationData FindInstrumentableInstructionsInBlock(BasicBlock &BB,
366-
OP *HlslOP);
366+
BlockInstrumentationData FindInstrumentableInstructionsInBlock(
367+
BasicBlock &BB, OP *HlslOP,
368+
llvm::SmallPtrSetImpl<Value *> const &RayQueryHandles);
367369
uint32_t
368370
CountBlockPayloadBytes(std::vector<InstructionToInstrument> const &IsAndTs);
369371
};
@@ -1003,10 +1005,6 @@ DxilDebugInstrumentation::addStoreStepDebugEntry(BuilderContext *BC,
10031005
return std::nullopt;
10041006
}
10051007

1006-
if (PIXPassHelpers::IsAllocateRayQueryInstruction(Inst->getValueOperand())) {
1007-
return std::nullopt;
1008-
}
1009-
10101008
auto Type = addStepDebugEntryValue(BC, InstNum, Inst->getValueOperand(),
10111009
ValueOrdinalBase, ValueOrdinalIndex);
10121010
if (Type) {
@@ -1054,20 +1052,25 @@ DxilDebugInstrumentation::addStoreStepDebugEntry(BuilderContext *BC,
10541052
return std::nullopt;
10551053
}
10561054

1057-
std::optional<InstructionAndType>
1058-
DxilDebugInstrumentation::addStepDebugEntry(BuilderContext *BC,
1059-
Instruction *Inst) {
1060-
if (PIXPassHelpers::IsAllocateRayQueryInstruction(Inst)) {
1055+
std::optional<InstructionAndType> DxilDebugInstrumentation::addStepDebugEntry(
1056+
BuilderContext *BC, Instruction *Inst,
1057+
llvm::SmallPtrSetImpl<Value *> const &RayQueryHandles) {
1058+
1059+
std::uint32_t InstNum;
1060+
if (!pix_dxil::PixDxilInstNum::FromInst(Inst, &InstNum)) {
10611061
return std::nullopt;
10621062
}
10631063

1064-
if (auto *St = llvm::dyn_cast<llvm::StoreInst>(Inst)) {
1065-
return addStoreStepDebugEntry(BC, St);
1064+
if (RayQueryHandles.count(Inst) != 0) {
1065+
InstructionAndType ret{};
1066+
ret.Inst = Inst;
1067+
ret.InstructionOrdinal = InstNum;
1068+
ret.Type = DebugShaderModifierRecordTypeDXILStepVoid;
1069+
return ret;
10661070
}
10671071

1068-
std::uint32_t InstNum;
1069-
if (!pix_dxil::PixDxilInstNum::FromInst(Inst, &InstNum)) {
1070-
return std::nullopt;
1072+
if (auto *St = llvm::dyn_cast<llvm::StoreInst>(Inst)) {
1073+
return addStoreStepDebugEntry(BC, St);
10711074
}
10721075

10731076
if (auto *Ld = llvm::dyn_cast<llvm::LoadInst>(Inst)) {
@@ -1300,8 +1303,9 @@ Instruction *FindFirstNonPhiInstruction(Instruction *I) {
13001303
// If indicator is "d", a single integer denoting the base for the alloca
13011304
// store.
13021305
DxilDebugInstrumentation::BlockInstrumentationData
1303-
DxilDebugInstrumentation::FindInstrumentableInstructionsInBlock(BasicBlock &BB,
1304-
OP *HlslOP) {
1306+
DxilDebugInstrumentation::FindInstrumentableInstructionsInBlock(
1307+
BasicBlock &BB, OP *HlslOP,
1308+
llvm::SmallPtrSetImpl<Value *> const &RayQueryHandles) {
13051309
BlockInstrumentationData ret{};
13061310
auto &Is = BB.getInstList();
13071311
*OSOverride << "Block#";
@@ -1315,7 +1319,7 @@ DxilDebugInstrumentation::FindInstrumentableInstructionsInBlock(BasicBlock &BB,
13151319
FoundFirstInstruction = true;
13161320
}
13171321
}
1318-
auto IandT = addStepDebugEntry(nullptr, &Inst);
1322+
auto IandT = addStepDebugEntry(nullptr, &Inst, RayQueryHandles);
13191323
if (IandT) {
13201324
InstructionToInstrument DebugOutputForThisInstruction{};
13211325
DebugOutputForThisInstruction.ValueType = IandT->Type;
@@ -1394,6 +1398,8 @@ bool DxilDebugInstrumentation::RunOnFunction(Module &M, DxilModule &DM,
13941398
default:
13951399
return false;
13961400
}
1401+
llvm::SmallPtrSet<Value *, 16> RayQueryHandles;
1402+
PIXPassHelpers::FindRayQueryHandlesForFunction(function, RayQueryHandles);
13971403

13981404
Instruction *firstInsertionPt = dxilutil::FirstNonAllocaInsertionPt(function);
13991405
IRBuilder<> Builder(firstInsertionPt);
@@ -1436,7 +1442,7 @@ bool DxilDebugInstrumentation::RunOnFunction(Module &M, DxilModule &DM,
14361442
values.AddedBlocksToIgnoreForInstrumentation.end(),
14371443
&BB) == values.AddedBlocksToIgnoreForInstrumentation.end()) {
14381444
auto BlockInstrumentation =
1439-
FindInstrumentableInstructionsInBlock(BB, BC.HlslOP);
1445+
FindInstrumentableInstructionsInBlock(BB, BC.HlslOP, RayQueryHandles);
14401446
if (BlockInstrumentation.FirstInstructionOrdinalInBlock <
14411447
m_FirstInstruction ||
14421448
BlockInstrumentation.FirstInstructionOrdinalInBlock >=

lib/DxilPIXPasses/PixPassHelpers.cpp

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,33 @@ using namespace llvm;
3737
using namespace hlsl;
3838

3939
namespace PIXPassHelpers {
40-
bool IsAllocateRayQueryInstruction(llvm::Value const *Val) {
41-
if (Val != nullptr) {
42-
if (llvm::Instruction const *Inst =
43-
llvm::dyn_cast<llvm::Instruction>(Val)) {
44-
return hlsl::OP::IsDxilOpFuncCallInst(Inst,
45-
hlsl::OP::OpCode::AllocateRayQuery);
40+
static void FindRayQueryHandlesFromUse(Value *U,
41+
SmallPtrSetImpl<Value *> &Handles) {
42+
if (Handles.insert(U).second) {
43+
auto RayQueryHandleUses = U->uses();
44+
for (Use &Use : RayQueryHandleUses) {
45+
iterator_range<Value::user_iterator> Users = Use->users();
46+
for (User *User : Users) {
47+
if (isa<PHINode>(User) || isa<SelectInst>(User))
48+
FindRayQueryHandlesFromUse(User, Handles);
49+
}
50+
}
51+
}
52+
}
53+
54+
void FindRayQueryHandlesForFunction(llvm::Function *F,
55+
SmallPtrSetImpl<Value *> &RayQueryHandles) {
56+
auto &blocks = F->getBasicBlockList();
57+
if (!blocks.empty()) {
58+
for (auto &block : blocks) {
59+
for (auto &instruction : block) {
60+
if (hlsl::OP::IsDxilOpFuncCallInst(
61+
&instruction, hlsl::OP::OpCode::AllocateRayQuery)) {
62+
FindRayQueryHandlesFromUse(&instruction, RayQueryHandles);
63+
}
64+
}
4665
}
4766
}
48-
return false;
4967
}
5068

5169
static unsigned int

lib/DxilPIXPasses/PixPassHelpers.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
#pragma once
1111

12+
#include <vector>
13+
1214
#include "dxc/DXIL/DxilModule.h"
1315
#include "llvm/IR/DebugInfoMetadata.h"
1416
#include "llvm/IR/IRBuilder.h"
@@ -30,7 +32,8 @@ class ScopedInstruction {
3032
llvm::Instruction *Get() const { return m_Instruction; }
3133
};
3234

33-
bool IsAllocateRayQueryInstruction(llvm::Value const *Val);
35+
void FindRayQueryHandlesForFunction(
36+
llvm::Function *F, llvm::SmallPtrSetImpl<llvm::Value *> &RayQueryHandles);
3437
llvm::CallInst *CreateUAV(hlsl::DxilModule &DM, llvm::IRBuilder<> &Builder,
3538
unsigned int registerId, const char *name);
3639
llvm::CallInst *CreateHandleForResource(hlsl::DxilModule &DM,

tools/clang/test/HLSLFileCheck/pix/TraceRayInline.hlsl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,21 @@
44
// CHECK: call void @dx.op.bufferStore.i32(i32 69, %dx.types.Handle
55

66
RaytracingAccelerationStructure RTAS;
7+
RWStructuredBuffer<float> UAV : register(u0);
78

89
void DoTrace(RayQuery<RAY_FLAG_FORCE_OPAQUE|RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES> rayQuery, RayDesc rayDesc) {
910
rayQuery.TraceRayInline(RTAS, 0, 1, rayDesc);
1011
}
1112

1213
float main(RayDesc rayDesc : RAYDESC) : OUT {
13-
RayQuery<RAY_FLAG_FORCE_OPAQUE|RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES> rayQuery;
14+
RayQuery<RAY_FLAG_FORCE_OPAQUE | RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES> rayQuery;
1415
DoTrace(rayQuery, rayDesc);
1516
rayQuery.TraceRayInline(RTAS, 1, 2, rayDesc);
17+
while (rayQuery.Proceed()) {
18+
UAV[0] = rayQuery.CandidateGeometryIndex();
19+
UAV[1] = rayQuery.CandidatePrimitiveIndex();
20+
UAV[2] = rayQuery.CandidateTriangleBarycentrics().x;
21+
rayQuery.CommitNonOpaqueTriangleHit();
22+
}
1623
return 0;
1724
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// RUN: %dxc -T vs_6_5 -Od -E main %s | %opt -S -dxil-annotate-with-virtual-regs -hlsl-dxil-debug-instrumentation | FileCheck %s
2+
3+
// CHECK: [[RAYQUERY0:%.*]] = call i32 @dx.op.allocateRayQuery
4+
// CHECK: [[RAYQUERY1:%.*]] = call i32 @dx.op.allocateRayQuery
5+
6+
// CHECK: [[PHIQUERY:%.*]] = phi i32 [ [[RAYQUERY0:%.*]], {{.*}} ], [ [[RAYQUERY0:%.*]], {{.*}} ]
7+
8+
// The debug instrumentation should NOT try to store this phi value: it's not an i32! (It's an opaque handle).
9+
// CHECK-NOT: @dx.op.bufferStore{{.*}}[[PHIQUERY]]
10+
11+
RaytracingAccelerationStructure RTAS;
12+
RWStructuredBuffer<int> UAV : register(u0);
13+
14+
float main(RayDesc rayDesc
15+
: RAYDESC) : OUT {
16+
RayQuery<RAY_FLAG_FORCE_OPAQUE | RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES> rayQuery0;
17+
RayQuery<RAY_FLAG_FORCE_OPAQUE | RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES> rayQuery1;
18+
rayQuery0.TraceRayInline(RTAS, 1, 2, rayDesc);
19+
rayQuery1.TraceRayInline(RTAS, 1, 2, rayDesc);
20+
RayQuery<RAY_FLAG_FORCE_OPAQUE | RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES> usedQuery = rayQuery0;
21+
if (UAV[0] == 1)
22+
usedQuery = rayQuery1;
23+
UAV[1] = usedQuery.CandidatePrimitiveIndex();
24+
rayQuery0.CommitNonOpaqueTriangleHit();
25+
if (rayQuery0.CommittedStatus() == COMMITTED_TRIANGLE_HIT) {
26+
UAV[4] = 42;
27+
}
28+
29+
return 0;
30+
}

0 commit comments

Comments
 (0)