Skip to content

Commit

Permalink
DXIL Debugger capture Phi referenced SSA IDs at branch point
Browse files Browse the repository at this point in the history
If control reached the current block from Parent i, Result Id gets the value that Variable i had at the end of Parent i.
  • Loading branch information
Zorro666 committed Nov 20, 2024
1 parent 2f29809 commit badc91f
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 60 deletions.
153 changes: 96 additions & 57 deletions renderdoc/driver/shaders/dxil/dxil_debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1505,6 +1505,7 @@ void ThreadState::EnterFunction(const Function *function, const rdcarray<Value *
m_Dormant.clear();
m_Block = 0;
m_PreviousBlock = ~0U;
m_PhiVariables.clear();

m_GlobalInstructionIdx = m_FunctionInfo->globalInstructionOffset + m_FunctionInstructionIdx;
m_Callstack.push_back(frame);
Expand Down Expand Up @@ -2859,6 +2860,15 @@ bool ThreadState::ExecuteInstruction(DebugAPIWrapper *apiWrapper,
case Operation::Branch:
{
m_PreviousBlock = m_Block;
m_PhiVariables.clear();
auto it = m_FunctionInfo->phiReferencedIdsPerBlock.find(m_PreviousBlock);
if(it != m_FunctionInfo->phiReferencedIdsPerBlock.end())
{
const ReferencedIds &phiIds = it->second;
for(Id id : phiIds)
m_PhiVariables[id] = m_Variables[id];
}

// Branch <label>
// Branch <label_true> <label_false> <BOOL_VAR>
uint32_t targetArg = 0;
Expand Down Expand Up @@ -2904,7 +2914,7 @@ bool ThreadState::ExecuteInstruction(DebugAPIWrapper *apiWrapper,
if(dxilValue)
{
ShaderVariable arg;
RDCASSERT(GetShaderVariable(dxilValue, opCode, dxOpCode, arg));
RDCASSERT(GetPhiShaderVariable(dxilValue, opCode, dxOpCode, arg));
result.value = arg.value;
break;
}
Expand Down Expand Up @@ -4168,8 +4178,9 @@ void ThreadState::StepNext(ShaderDebugState *state, DebugAPIWrapper *apiWrapper,
m_State = NULL;
}

bool ThreadState::GetShaderVariable(const DXIL::Value *dxilValue, Operation op, DXOp dxOpCode,
ShaderVariable &var, bool flushDenormInput) const
bool ThreadState::GetShaderVariableHelper(const DXIL::Value *dxilValue, DXIL::Operation op,
DXIL::DXOp dxOpCode, ShaderVariable &var,
bool flushDenormInput, bool isLive) const
{
var.name.clear();
var.members.clear();
Expand Down Expand Up @@ -4270,21 +4281,39 @@ bool ThreadState::GetShaderVariable(const DXIL::Value *dxilValue, Operation op,

if(const Instruction *inst = cast<Instruction>(dxilValue))
{
GetVariable(inst->slot, op, dxOpCode, var);
return true;
if(isLive)
return GetLiveVariable(inst->slot, op, dxOpCode, var);
else
return GetPhiVariable(inst->slot, op, dxOpCode, var);
}
RDCERR("Unhandled DXIL Value type");

return false;
}

bool ThreadState::GetVariable(const Id &id, Operation op, DXOp dxOpCode, ShaderVariable &var) const
bool ThreadState::GetLiveVariable(const Id &id, Operation op, DXOp dxOpCode, ShaderVariable &var) const
{
RDCASSERT(m_Live.contains(id));
auto it = m_Variables.find(id);
RDCASSERT(it != m_Variables.end());
var = it->second;
return GetVariableHelper(op, dxOpCode, var);
}

bool ThreadState::GetPhiVariable(const Id &id, Operation op, DXOp dxOpCode, ShaderVariable &var) const
{
auto it = m_PhiVariables.find(id);
if(it != m_PhiVariables.end())
{
var = it->second;
return GetVariableHelper(op, dxOpCode, var);
}
RDCERR("Phi Variable not found %d", id);
return false;
}

bool ThreadState::GetVariableHelper(Operation op, DXOp dxOpCode, ShaderVariable &var) const
{
bool flushDenorm = OperationFlushing(op, dxOpCode);
if(var.type == VarType::Double)
flushDenorm = false;
Expand Down Expand Up @@ -4772,8 +4801,8 @@ ShaderValue ThreadState::DDX(bool fine, Operation opCode, DXOp dxOpCode,
ShaderValue ret;
ShaderVariable a;
ShaderVariable b;
RDCASSERT(quad[index + 1].GetVariable(id, opCode, dxOpCode, a));
RDCASSERT(quad[index].GetVariable(id, opCode, dxOpCode, b));
RDCASSERT(quad[index + 1].GetLiveVariable(id, opCode, dxOpCode, a));
RDCASSERT(quad[index].GetLiveVariable(id, opCode, dxOpCode, b));
Sub(a, b, ret);
return ret;
}
Expand Down Expand Up @@ -4803,8 +4832,8 @@ ShaderValue ThreadState::DDY(bool fine, Operation opCode, DXOp dxOpCode,
ShaderValue ret;
ShaderVariable a;
ShaderVariable b;
RDCASSERT(quad[index + 2].GetVariable(id, opCode, dxOpCode, a));
RDCASSERT(quad[index].GetVariable(id, opCode, dxOpCode, b));
RDCASSERT(quad[index + 2].GetLiveVariable(id, opCode, dxOpCode, a));
RDCASSERT(quad[index].GetLiveVariable(id, opCode, dxOpCode, b));
Sub(a, b, ret);
return ret;
}
Expand Down Expand Up @@ -6105,68 +6134,78 @@ ShaderDebugTrace *Debugger::BeginDebug(uint32_t eventId, const DXBC::DXBCContain
if(DXIL::IsDXCNop(inst) || DXIL::IsLLVMDebugCall(inst))
continue;

// Stack allocations last until the end of the function
// Allow the variable to live for one instruction longer
const uint32_t maxInst = i + 1;
const uint32_t maxInst = (inst.op == Operation::Alloca) ? countInstructions : i + 1;
Id resultId = inst.slot;
if(resultId != DXILDebug::INVALID_ID)
{
Id resultId = inst.slot;
if(resultId != DXILDebug::INVALID_ID)
{
// The result SSA should not have been referenced before
RDCASSERTEQUAL(ssaRefs.count(resultId), 0);
ssaRefs.insert(resultId);

// For assignment track maximum and minimum (as current instruction plus one)
auto itRange = ssaRange.find(resultId);
if(itRange == ssaRange.end())
{
ssaRange[resultId] = {i + 1, maxInst};
}
else
{
itRange->second.min = RDCMIN(i + 1, itRange->second.min);
itRange->second.max = RDCMAX(maxInst, itRange->second.max);
}

// Stack allocations last until the end of the function
if(inst.op == Operation::Alloca)
itRange->second.max = countInstructions;
}
// The result SSA should not have been referenced before
RDCASSERTEQUAL(ssaRefs.count(resultId), 0);
ssaRefs.insert(resultId);

// The result SSA should not have any range tracking
RDCASSERTEQUAL(ssaRange.count(resultId), 0);
// For assignment track maximum and minimum (as current instruction plus one)
ssaRange[resultId] = {i + 1, maxInst};
}
// Track min and max when SSA is referenced
bool isPhiNode = (inst.op == Operation::Phi);
// Track min and max when SSA is referenced as an argument
// Arguments to phi instructions are handled separately
if(inst.op == Operation::Phi)
continue;
for(uint32_t a = 0; a < inst.args.size(); ++a)
{
DXIL::Value *arg = inst.args[a];
if(DXIL::IsSSA(arg))
if(!DXIL::IsSSA(arg))
continue;
Id argId = GetSSAId(arg);
// Add GlobalVar args to the SSA refs (they won't be the result of an instruction)
if(cast<GlobalVar>(arg))
{
Id argId = GetSSAId(arg);
// Add GlobalVar args to the SSA refs (they won't be the result of an instruction)
if(cast<GlobalVar>(arg))
{
if(ssaRefs.count(argId) == 0)
ssaRefs.insert(argId);
}
if(!isPhiNode)
if(ssaRefs.count(argId) == 0)
{
// For non phi-nodes the argument SSA should already exist as the result of a previous operation
RDCASSERTEQUAL(ssaRefs.count(argId), 1);
}
auto itRange = ssaRange.find(argId);
if(itRange == ssaRange.end())
{
ssaRange[argId] = {i, maxInst};
}
else
{
itRange->second.min = RDCMIN(i, itRange->second.min);
itRange->second.max = RDCMAX(maxInst, itRange->second.max);
ssaRefs.insert(argId);
ssaRange[argId] = {0, maxInst};
}
}
// For non phi-nodes the argument SSA should already exist as the result of a previous operation
RDCASSERTEQUAL(ssaRefs.count(argId), 1);
// The result SSA should not have any range tracking
auto itRange = ssaRange.find(argId);
if(itRange != ssaRange.end())
{
itRange->second.min = RDCMIN(i, itRange->second.min);
itRange->second.max = RDCMAX(maxInst, itRange->second.max);
}
else
{
RDCERR("SSA Id range not found %d", argId);
}
}
}
// If these do not match in size that means there is a result SSA that is never read
RDCASSERTEQUAL(ssaRefs.size(), ssaRange.size());

// store the block captured SSA IDs used as arguments to phi nodes
PhiReferencedIdsPerBlock &phiReferencedIdsPerBlock = info.phiReferencedIdsPerBlock;
for(uint32_t i = 0; i < countInstructions; ++i)
{
const Instruction &inst = *(f->instructions[i]);
if(inst.op != Operation::Phi)
continue;
for(uint32_t a = 0; a < inst.args.size(); a += 2)
{
DXIL::Value *arg = inst.args[a];
if(!DXIL::IsSSA(arg))
continue;
Id argId = GetSSAId(arg);
const Block *block = cast<Block>(inst.args[a + 1]);
RDCASSERT(block);
uint32_t blockId = block->id;
phiReferencedIdsPerBlock[blockId].insert(argId);
}
}

// Find the uniform control blocks in the function
rdcarray<rdcpair<uint32_t, uint32_t>> links;
for(const Block *block : f->blocks)
Expand Down
26 changes: 23 additions & 3 deletions renderdoc/driver/shaders/dxil/dxil_debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ struct InstructionRange
typedef std::map<ShaderBuiltin, ShaderVariable> BuiltinInputs;
typedef std::set<Id> ReferencedIds;
typedef std::map<Id, InstructionRange> InstructionRangePerId;
typedef std::map<uint32_t, ReferencedIds> PhiReferencedIdsPerBlock;

void GetInterpolationModeForInputParams(const rdcarray<SigParameter> &stageInputSig,
const DXIL::Program *program,
Expand Down Expand Up @@ -86,6 +87,7 @@ struct FunctionInfo
const DXIL::Function *function = NULL;
ReferencedIds referencedIds;
InstructionRangePerId rangePerId;
PhiReferencedIdsPerBlock phiReferencedIdsPerBlock;
uint32_t globalInstructionOffset = ~0U;
rdcarray<uint32_t> uniformBlocks;
};
Expand Down Expand Up @@ -157,10 +159,24 @@ struct ThreadState
Id GetArgumentId(uint32_t i) const;
const DXIL::ResourceReference *GetResource(Id handleId, ShaderBindIndex &bindIndex,
bool &annotatedHandle);

bool GetShaderVariable(const DXIL::Value *dxilValue, DXIL::Operation op, DXIL::DXOp dxOpCode,
ShaderVariable &var, bool flushDenormInput = true) const;
bool GetVariable(const Id &id, DXIL::Operation opCode, DXIL::DXOp dxOpCode,
ShaderVariable &var) const;
ShaderVariable &var, bool flushDenormInput = true) const
{
return GetShaderVariableHelper(dxilValue, op, dxOpCode, var, flushDenormInput, true);
}

bool GetPhiShaderVariable(const DXIL::Value *dxilValue, DXIL::Operation op, DXIL::DXOp dxOpCode,
ShaderVariable &var, bool flushDenormInput = true) const
{
return GetShaderVariableHelper(dxilValue, op, dxOpCode, var, flushDenormInput, false);
}

bool GetLiveVariable(const Id &id, DXIL::Operation opCode, DXIL::DXOp dxOpCode,
ShaderVariable &var) const;
bool GetPhiVariable(const Id &id, DXIL::Operation opCode, DXIL::DXOp dxOpCode,
ShaderVariable &var) const;
bool GetVariableHelper(DXIL::Operation op, DXIL::DXOp dxOpCode, ShaderVariable &var) const;
void AllocateMemoryForType(const DXIL::Type *type, Id allocId, ShaderVariable &var);
void UpdateBackingMemoryFromVariable(void *ptr, size_t &allocSize, const ShaderVariable &var);
void UpdateMemoryVariableFromBackingMemory(Id memoryId, const void *ptr);
Expand All @@ -181,6 +197,8 @@ struct ThreadState
void InitialiseHelper(const ThreadState &activeState);
static bool ThreadsAreDiverged(const rdcarray<ThreadState> &workgroups);

bool GetShaderVariableHelper(const DXIL::Value *dxilValue, DXIL::Operation op, DXIL::DXOp dxOpCode,
ShaderVariable &var, bool flushDenormInput, bool isLive) const;
struct MemoryAlloc
{
void *backingMemory;
Expand Down Expand Up @@ -220,6 +238,8 @@ struct ThreadState

// Known SSA ShaderVariables
std::map<Id, ShaderVariable> m_Variables;
// SSA Variables captured when a branch happens for use in phi nodes
std::map<Id, ShaderVariable> m_PhiVariables;
// Live variables at the current scope
rdcarray<Id> m_Live;
// Dormant variables at the current scope
Expand Down

0 comments on commit badc91f

Please sign in to comment.