Store source vars data per-instruction rather than per-state

* Since the source vars data doesn't change for a given instruction, we can pre-
  calculate it and save time on re-calculating per-state.
* Note callstack *can* change per-state on SPIR-V where the same instruction can
  be reached by different flow paths, so the callstack remains part of the per-
  state data.
This commit is contained in:
baldurk
2022-08-03 17:44:11 +01:00
parent b06df5dd47
commit 935fa49c98
12 changed files with 622 additions and 469 deletions
+1
View File
@@ -373,6 +373,7 @@ TEMPLATE_ARRAY_INSTANTIATE(rdcarray, PixelModification)
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ResourceDescription)
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ResourceId)
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, LineColumnInfo)
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, InstructionSourceInfo)
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderCompileFlag)
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderConstant)
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderDebugState)
+112 -31
View File
@@ -631,9 +631,11 @@ void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderR
bool hasLineInfo = false;
LineColumnInfo prevLine;
for(uint32_t inst = 0; inst < m_Trace->lineInfo.size(); inst++)
for(uint32_t inst = 0; inst < m_Trace->instInfo.size(); inst++)
{
LineColumnInfo line = m_Trace->lineInfo[inst];
const InstructionSourceInfo &instInfo = m_Trace->instInfo[inst];
LineColumnInfo line = instInfo.lineInfo;
int disasmLine = (int)line.disassemblyLine;
if(disasmLine > 0 && disasmLine >= m_AsmLine2Inst.size())
@@ -930,7 +932,7 @@ void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderR
{
applyForwardsChange();
if(m_Trace->lineInfo[GetCurrentState().nextInstruction].fileIndex >= 0)
if(GetCurrentInstInfo().lineInfo.fileIndex >= 0)
break;
if(IsLastState())
@@ -2029,7 +2031,7 @@ bool ShaderViewer::step(bool forward, StepMode mode)
if(isSourceDebugging())
{
LineColumnInfo oldLine = m_Trace->lineInfo[GetCurrentState().nextInstruction];
LineColumnInfo oldLine = GetCurrentInstInfo().lineInfo;
rdcarray<rdcstr> oldStack = GetCurrentState().callstack;
do
@@ -2040,7 +2042,7 @@ bool ShaderViewer::step(bool forward, StepMode mode)
else
applyBackwardsChange();
LineColumnInfo curLine = m_Trace->lineInfo[GetCurrentState().nextInstruction];
LineColumnInfo curLine = GetCurrentLineInfo();
// break out if we hit a breakpoint, no matter what
if(m_Breakpoints.contains({-1, curLine.disassemblyLine}) ||
@@ -2121,7 +2123,7 @@ bool ShaderViewer::step(bool forward, StepMode mode)
} while(true);
oldLine = m_Trace->lineInfo[GetCurrentState().nextInstruction];
oldLine = GetCurrentLineInfo();
oldStack = GetCurrentState().callstack;
if(!forward)
@@ -2144,7 +2146,7 @@ bool ShaderViewer::step(bool forward, StepMode mode)
{
if((oldStack == GetPreviousState().callstack ||
oldStack.size() > GetPreviousState().callstack.size()) &&
!oldLine.SourceEqual(m_Trace->lineInfo[GetPreviousState().nextInstruction]))
!oldLine.SourceEqual(GetPreviousInstInfo().lineInfo))
{
// if we hit this case, it means we jumped to an instruction in the same call which maps
// to a different line (perhaps through a loop or branch), or we lost a function in the
@@ -2155,7 +2157,7 @@ bool ShaderViewer::step(bool forward, StepMode mode)
applyBackwardsChange();
LineColumnInfo curLine = m_Trace->lineInfo[GetCurrentState().nextInstruction];
LineColumnInfo curLine = GetCurrentLineInfo();
// still need to check for instruction-level breakpoints
if(m_Breakpoints.contains({-1, curLine.disassemblyLine}))
@@ -2164,12 +2166,11 @@ bool ShaderViewer::step(bool forward, StepMode mode)
}
else
{
while(!IsFirstState() &&
m_Trace->lineInfo[GetPreviousState().nextInstruction].SourceEqual(oldLine))
while(!IsFirstState() && GetPreviousInstInfo().lineInfo.SourceEqual(oldLine))
{
applyBackwardsChange();
LineColumnInfo curLine = m_Trace->lineInfo[GetCurrentState().nextInstruction];
LineColumnInfo curLine = GetCurrentLineInfo();
// still need to check for instruction-level breakpoints
if(m_Breakpoints.contains({-1, curLine.disassemblyLine}))
@@ -2262,6 +2263,33 @@ const ShaderDebugState &ShaderViewer::GetNextState() const
return m_States.back();
}
const InstructionSourceInfo &ShaderViewer::GetPreviousInstInfo() const
{
return GetInstInfo(GetPreviousState().nextInstruction);
}
const InstructionSourceInfo &ShaderViewer::GetCurrentInstInfo() const
{
return GetInstInfo(GetCurrentState().nextInstruction);
}
const InstructionSourceInfo &ShaderViewer::GetNextInstInfo() const
{
return GetInstInfo(GetNextState().nextInstruction);
}
const InstructionSourceInfo &ShaderViewer::GetInstInfo(uint32_t instruction) const
{
InstructionSourceInfo search;
search.instruction = instruction;
return *std::lower_bound(m_Trace->instInfo.begin(), m_Trace->instInfo.end(), search);
}
const LineColumnInfo &ShaderViewer::GetCurrentLineInfo() const
{
return GetCurrentInstInfo().lineInfo;
}
void ShaderViewer::runTo(uint32_t runToInstruction, bool forward, ShaderEvents condition)
{
rdcarray<uint32_t> insts = {runToInstruction};
@@ -2275,7 +2303,7 @@ void ShaderViewer::runTo(const rdcarray<uint32_t> &runToInstructions, bool forwa
return;
bool firstStep = true;
LineColumnInfo oldLine = m_Trace->lineInfo[GetCurrentState().nextInstruction];
LineColumnInfo oldLine = GetCurrentLineInfo();
// this is effectively infinite as we break out before moving to next/previous state if that would
// be first/last
@@ -2290,7 +2318,7 @@ void ShaderViewer::runTo(const rdcarray<uint32_t> &runToInstructions, bool forwa
break;
// or breakpoint
LineColumnInfo curLine = m_Trace->lineInfo[GetCurrentState().nextInstruction];
LineColumnInfo curLine = GetCurrentLineInfo();
if(!firstStep && (m_Breakpoints.contains({-1, curLine.disassemblyLine}) ||
m_Breakpoints.contains({curLine.fileIndex, curLine.lineStart})))
break;
@@ -2362,7 +2390,7 @@ void ShaderViewer::runToResourceAccess(bool forward, VarType type, const Bindpoi
break;
// or breakpoint
LineColumnInfo curLine = m_Trace->lineInfo[GetCurrentState().nextInstruction];
LineColumnInfo curLine = GetCurrentLineInfo();
if(m_Breakpoints.contains({-1, curLine.disassemblyLine}) ||
m_Breakpoints.contains({curLine.fileIndex, curLine.lineStart}))
break;
@@ -2731,9 +2759,9 @@ QString ShaderViewer::getRegNames(const RDTreeWidgetItem *item, uint32_t swizzle
mapping = m_Trace->sourceVars[tag.sourceVarIdx];
}
else if(!tag.globalSourceVar && tag.sourceVarIdx >= 0 &&
tag.sourceVarIdx < GetCurrentState().sourceVars.count())
tag.sourceVarIdx < GetCurrentInstInfo().sourceVars.count())
{
mapping = GetCurrentState().sourceVars[tag.sourceVarIdx];
mapping = GetCurrentInstInfo().sourceVars[tag.sourceVarIdx];
}
else
{
@@ -2991,9 +3019,9 @@ const RDTreeWidgetItem *ShaderViewer::evaluateVar(const RDTreeWidgetItem *item,
mapping = m_Trace->sourceVars[tag.sourceVarIdx];
}
else if(!tag.globalSourceVar && tag.sourceVarIdx >= 0 &&
tag.sourceVarIdx < GetCurrentState().sourceVars.count())
tag.sourceVarIdx < GetCurrentInstInfo().sourceVars.count())
{
mapping = GetCurrentState().sourceVars[tag.sourceVarIdx];
mapping = GetCurrentInstInfo().sourceVars[tag.sourceVarIdx];
}
else
{
@@ -3515,9 +3543,8 @@ void ShaderViewer::updateDebugState()
for(const rdcstr &s : state.callstack)
ui->callstack->insertItem(0, s);
if(state.nextInstruction < m_Trace->lineInfo.size())
{
LineColumnInfo lineInfo = m_Trace->lineInfo[state.nextInstruction];
LineColumnInfo lineInfo = GetInstInfo(state.nextInstruction).lineInfo;
// highlight the current line
{
@@ -3538,7 +3565,7 @@ void ShaderViewer::updateDebugState()
// last state which did.
for(int stateLookbackIdx = (int)m_CurrentStateIdx; stateLookbackIdx > 0; stateLookbackIdx--)
{
lineInfo = m_Trace->lineInfo[m_States[stateLookbackIdx].nextInstruction];
lineInfo = GetInstInfo(m_States[stateLookbackIdx].nextInstruction).lineInfo;
if(lineInfo.fileIndex >= 0 && lineInfo.fileIndex < m_FileScintillas.count())
break;
@@ -3916,21 +3943,22 @@ void ShaderViewer::updateDebugState()
RDTreeWidgetItem fakeroot;
const rdcarray<SourceVariableMapping> &sourceVars = state.sourceVars;
for(size_t lidx = 0; lidx < sourceVars.size(); lidx++)
for(int globalVarIdx = 0; globalVarIdx < m_Trace->sourceVars.count(); globalVarIdx++)
{
int32_t localVarIdx = int32_t(sourceVars.size() - 1 - lidx);
const SourceVariableMapping &sourceVar = m_Trace->sourceVars[globalVarIdx];
// iterate in reverse order, so newest locals tend to end up on top
const SourceVariableMapping &l = sourceVars[localVarIdx];
if(!sourceVar.variables.empty() && sourceVar.variables[0].type != DebugVariableType::Variable)
continue;
if(sourceVar.rows == 0 || sourceVar.columns == 0)
continue;
bool modified = false;
// don't list any modified variables on the first step when they all come into existance
if(l.variables[0].type == DebugVariableType::Variable && !IsFirstState())
if(sourceVar.variables[0].type == DebugVariableType::Variable && !IsFirstState())
{
for(const DebugVariableReference &v : l.variables)
for(const DebugVariableReference &v : sourceVar.variables)
{
rdcstr base = v.name;
int offs = base.find_first_of("[.");
@@ -3952,6 +3980,59 @@ void ShaderViewer::updateDebugState()
}
}
fakeroot.addChild(makeSourceVariableNode(sourceVar, globalVarIdx, -1, modified));
}
const rdcarray<SourceVariableMapping> &sourceVars = GetCurrentInstInfo().sourceVars;
for(size_t lidx = 0; lidx < sourceVars.size(); lidx++)
{
int32_t localVarIdx = int32_t(sourceVars.size() - 1 - lidx);
// iterate in reverse order, so newest locals tend to end up on top
const SourceVariableMapping &l = sourceVars[localVarIdx];
bool modified = false;
bool hasNonError = false;
for(const DebugVariableReference &v : l.variables)
{
hasNonError |= (GetDebugVariable(v) != NULL);
// don't list any modified variables on the first step when they all come into existance
if(IsFirstState())
{
if(hasNonError)
break;
}
else
{
rdcstr base = v.name;
int offs = base.find_first_of("[.");
if(offs > 0)
base = v.name.substr(0, offs);
for(const ShaderVariableChange &c : GetCurrentState().changes)
{
if(c.before.name == v.name || c.after.name == v.name || c.before.name == base ||
c.after.name == base)
{
modified = true;
break;
}
}
if(modified && hasNonError)
break;
}
}
// don't display source variables that map to non-existant debug variables. This can happen
// when flow control means those debug variables were never created, but they would be mapped
// at this point.
if(!hasNonError)
continue;
RDTreeWidgetItem *node = makeSourceVariableNode(l, -1, localVarIdx, modified);
fakeroot.addChild(node);
@@ -4952,7 +5033,7 @@ void ShaderViewer::ToggleBreakpointOnInstruction(int32_t instruction)
if(instruction >= 0)
{
LineColumnInfo &instLine = m_Trace->lineInfo[instruction];
const LineColumnInfo &instLine = GetInstInfo(instruction).lineInfo;
sourceBreakpoint = {instLine.fileIndex, instLine.lineStart};
disasmBreakpoints.push_back({-1, instLine.disassemblyLine});
}
@@ -4988,7 +5069,7 @@ void ShaderViewer::ToggleBreakpointOnInstruction(int32_t instruction)
{
sourceBreakpoint = {scintillaIndex, (uint32_t)i};
for(uint32_t inst : it.value())
disasmBreakpoints.push_back({-1, m_Trace->lineInfo[inst].disassemblyLine});
disasmBreakpoints.push_back({-1, GetInstInfo(inst).lineInfo.disassemblyLine});
}
else
{
+6
View File
@@ -354,6 +354,12 @@ private:
const ShaderDebugState &GetCurrentState() const;
const ShaderDebugState &GetNextState() const;
const InstructionSourceInfo &GetPreviousInstInfo() const;
const InstructionSourceInfo &GetCurrentInstInfo() const;
const InstructionSourceInfo &GetNextInstInfo() const;
const InstructionSourceInfo &GetInstInfo(uint32_t instruction) const;
const LineColumnInfo &GetCurrentLineInfo() const;
void updateDebugState();
void markWatchStale(RDTreeWidgetItem *item);
bool updateWatchVariable(RDTreeWidgetItem *watchItem, const RDTreeWidgetItem *varItem,
+46 -19
View File
@@ -617,6 +617,37 @@ treated as covering the code.
};
DECLARE_REFLECTION_STRUCT(LineColumnInfo);
struct InstructionSourceInfo
{
DOCUMENT("");
InstructionSourceInfo() = default;
InstructionSourceInfo(const InstructionSourceInfo &) = default;
InstructionSourceInfo &operator=(const InstructionSourceInfo &) = default;
bool operator==(const InstructionSourceInfo &o) const { return instruction == o.instruction; }
bool operator<(const InstructionSourceInfo &o) const { return instruction < o.instruction; }
DOCUMENT("The instruction that this information is for.");
uint32_t instruction;
DOCUMENT(R"(The source location that this instruction corresponds to
:type: LineColumnInfo
)");
LineColumnInfo lineInfo;
DOCUMENT(R"(An optional mapping of which high-level source variables map to which debug variables
and including extra type information.
This list contains source variable mapping that is only valid at this instruction, and is fully
complete & redundant including all previous source variables that are still valid at this
instruction.
:type: List[SourceVariableMapping]
)");
rdcarray<SourceVariableMapping> sourceVars;
};
DECLARE_REFLECTION_STRUCT(InstructionSourceInfo);
DOCUMENT("This stores the before and after state of a :class:`ShaderVariable`.");
struct ShaderVariableChange
{
@@ -675,7 +706,7 @@ struct ShaderDebugState
bool operator==(const ShaderDebugState &o) const
{
return nextInstruction == o.nextInstruction && flags == o.flags && changes == o.changes &&
sourceVars == o.sourceVars && stepIndex == o.stepIndex;
stepIndex == o.stepIndex;
}
bool operator<(const ShaderDebugState &o) const
{
@@ -687,8 +718,6 @@ struct ShaderDebugState
return stepIndex < o.stepIndex;
if(!(changes == o.changes))
return changes < o.changes;
if(!(sourceVars == o.sourceVars))
return sourceVars < o.sourceVars;
return false;
}
@@ -713,18 +742,7 @@ backwards using the information.
)");
rdcarray<ShaderVariableChange> changes;
DOCUMENT(R"(An optional mapping of which high-level source variables map to which debug variables
and including extra type information.
This list contains source variable mapping that is only valid at this state - it is not valid at any
other state where the lifetime of the source variable may have run out, or it may now be stored in
a different debug variable.
:type: List[SourceVariableMapping]
)");
rdcarray<SourceVariableMapping> sourceVars;
DOCUMENT(R"(The function names in the current callstack at this line.
DOCUMENT(R"(The function names in the current callstack at this instruction.
The oldest/outer function is first in the list, the newest/inner function is last.
@@ -831,12 +849,21 @@ If this is ``None`` then the trace is invalid.
)");
ShaderDebugger *debugger = NULL;
DOCUMENT(R"(An array of the same size as the number of instructions in the shader, with a mapping
to source lines.
DOCUMENT(R"(An array of the same size as the number of instructions in the shader, with
per-instruction information such as source line mapping, and source variables.
:type: List[LineColumnInfo]
.. warning::
This array is *not* indexed by instruction. Since it is common for adjacent instructions to have
effectively identical source information, this array only stores unique information ordered by
instruction. On some internal representations this may be one entry per instruction, and on others
it may be sparse and require a binary lookup to locate the corresponding information for an
instruction. If no direct match is found, the lower bound match is valid (i.e. the data for
instruction A before the data for instruction B is valid for all instructions in range ``[A, B)``.
:type: List[InstructionSourceInfo]
)");
rdcarray<LineColumnInfo> lineInfo;
rdcarray<InstructionSourceInfo> instInfo;
};
DECLARE_REFLECTION_STRUCT(ShaderDebugTrace);
@@ -593,13 +593,13 @@ void DXBCContainer::FillTraceLineInfo(ShaderDebugTrace &trace) const
{
if(m_DXBCByteCode)
{
trace.lineInfo.resize(m_DXBCByteCode->GetNumInstructions());
trace.instInfo.resize(m_DXBCByteCode->GetNumInstructions());
for(size_t i = 0; i < m_DXBCByteCode->GetNumInstructions(); i++)
{
const DXBCBytecode::Operation &op = m_DXBCByteCode->GetInstruction(i);
if(m_DebugInfo)
m_DebugInfo->GetLineInfo(i, op.offset, trace.lineInfo[i]);
m_DebugInfo->GetLineInfo(i, op.offset, trace.instInfo[i].lineInfo);
// we add some number of lines for the header we added with shader hash, debug name, etc on
// top of what the bytecode disassembler did
@@ -615,38 +615,14 @@ void DXBCContainer::FillTraceLineInfo(ShaderDebugTrace &trace) const
extraLines += (uint32_t)Bits::CountOnes((uint32_t)m_GlobalFlags) + 2;
if(op.line > 0)
trace.lineInfo[i].disassemblyLine = extraLines + op.line;
trace.instInfo[i].lineInfo.disassemblyLine = extraLines + op.line;
if(m_DebugInfo)
m_DebugInfo->GetLocals(this, i, op.offset, trace.instInfo[i].sourceVars);
}
}
}
void DXBCContainer::FillStateInstructionInfo(ShaderDebugState &state) const
{
uint32_t instruction = state.nextInstruction;
uintptr_t offset = 0;
state.sourceVars.clear();
if(m_DXBCByteCode)
{
if(instruction < m_DXBCByteCode->GetNumInstructions())
offset = m_DXBCByteCode->GetInstruction(instruction).offset;
if(m_DebugInfo)
m_DebugInfo->GetLocals(this, instruction, offset, state.sourceVars);
}
if(m_DebugInfo)
{
m_DebugInfo->GetCallstack(instruction, offset, state.callstack);
}
else
{
state.callstack.clear();
}
}
void DXBCContainer::StripChunk(bytebuf &ByteCode, uint32_t fourcc)
{
FileHeader *header = (FileHeader *)ByteCode.data();
@@ -187,7 +187,6 @@ public:
const rdcstr &GetDisassembly();
void FillTraceLineInfo(ShaderDebugTrace &trace) const;
void FillStateInstructionInfo(ShaderDebugState &state) const;
static void StripChunk(bytebuf &ByteCode, uint32_t fourcc);
static void ReplaceChunk(bytebuf &ByteCode, uint32_t fourcc, const byte *replacement, size_t size);
+4 -2
View File
@@ -1151,6 +1151,7 @@ ThreadState::ThreadState(int workgroupIdx, GlobalState &globalState, const DXBC:
nextInstruction = 0;
reflection = dxbc->GetReflection();
program = dxbc->GetDXBCByteCode();
debug = dxbc->GetDebugInfo();
RDCEraseEl(semantics);
program->SetupRegisterFile(variables);
@@ -1952,6 +1953,9 @@ void ThreadState::StepNext(ShaderDebugState *state, DebugAPIWrapper *apiWrapper,
const Operation &op = program->GetInstruction((size_t)nextInstruction);
if(state && debug)
debug->GetCallstack(nextInstruction, op.offset, state->callstack);
apiWrapper->SetCurrentInstruction(nextInstruction);
nextInstruction++;
@@ -5647,7 +5651,6 @@ rdcarray<ShaderDebugState> InterpretDebugger::ContinueDebug(DXBCDebug::DebugAPIW
for(const ShaderVariable &v : active.variables)
initial.changes.push_back({ShaderVariable(), v});
dxbc->FillStateInstructionInfo(initial);
ret.push_back(std::move(initial));
@@ -5685,7 +5688,6 @@ rdcarray<ShaderDebugState> InterpretDebugger::ContinueDebug(DXBCDebug::DebugAPIW
workgroup[i].StepNext(&state, apiWrapper, oldworkgroup);
state.stepIndex = steps;
state.nextInstruction = workgroup[i].nextInstruction;
dxbc->FillStateInstructionInfo(state);
ret.push_back(std::move(state));
steps++;
@@ -323,6 +323,7 @@ private:
const DXBC::Reflection *reflection;
const DXBCBytecode::Program *program;
const DXBC::IDebugInfo *debug;
rdcarray<BindpointIndex> m_accessedSRVs;
rdcarray<BindpointIndex> m_accessedUAVs;
@@ -125,12 +125,10 @@ void ThreadState::EnterFunction(const rdcarray<Id> &arguments)
// process the outgoing scope
ProcessScopeChange(live, {});
callstack.back()->live = live;
callstack.back()->sourceVars = sourceVars;
}
// start with just globals
live = debugger.GetLiveGlobals();
sourceVars = debugger.GetGlobalSourceVars();
callstack.push_back(frame);
@@ -371,8 +369,6 @@ void ThreadState::SetDst(Id id, const ShaderVariable &val)
change.before = prev;
change.after = debugger.GetPointerValue(ids[id]);
m_State->changes.push_back(change);
debugger.AddSourceVars(sourceVars, change.after, id);
}
}
@@ -561,7 +557,6 @@ bool ThreadState::ReferencePointer(Id id)
{
if(!frame->localsUsed.contains(id))
{
debugger.AddSourceVars(sourceVars, frame->locals[i], id);
frame->localsUsed.push_back(id);
firstLocalWrite = true;
}
@@ -3051,7 +3046,6 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
// restore the live list from the calling frame
live = callstack.back()->live;
sourceVars = callstack.back()->sourceVars;
}
delete exitingFrame;
+37 -39
View File
@@ -148,7 +148,6 @@ struct StackFrame
// the thread's live list before the function was entered
rdcarray<Id> live;
rdcarray<SourceVariableMapping> sourceVars;
// the last block we were in and the current block, for OpPhis
Id lastBlock, curBlock;
@@ -219,8 +218,6 @@ struct ThreadState
std::map<Id, uint32_t> lastWrite;
rdcarray<SourceVariableMapping> sourceVars;
// index in the pixel quad
uint32_t workgroupIndex;
bool helperInvocation;
@@ -261,33 +258,6 @@ struct TypeData
rdcarray<rdcpair<rdcstr, Id>> structMembers;
};
struct ScopeData
{
DebugScope type;
ScopeData *parent;
uint32_t line;
uint32_t column;
int32_t fileIndex;
size_t end;
rdcstr name;
rdcarray<Id> locals;
};
struct InlineData
{
ScopeData *scope;
InlineData *parent;
};
struct LocalData
{
rdcstr name;
ScopeData *scope;
TypeData *type;
};
struct LocalMapping
{
bool operator<(const LocalMapping &o) const
@@ -322,13 +292,42 @@ struct LocalMapping
return true;
}
uint32_t stepIndex;
uint32_t instIndex;
Id sourceVar;
Id debugVar;
bool isDeclare;
rdcarray<uint32_t> indexes;
};
struct ScopeData
{
DebugScope type;
ScopeData *parent;
uint32_t line;
uint32_t column;
int32_t fileIndex;
size_t end;
rdcstr name;
rdcarray<Id> locals;
rdcarray<LocalMapping> localMappings;
};
struct InlineData
{
ScopeData *scope;
InlineData *parent;
};
struct LocalData
{
rdcstr name;
ScopeData *scope;
TypeData *type;
};
Id ParseRawName(const rdcstr &name);
rdcstr GetRawName(Id id);
@@ -345,8 +344,6 @@ public:
rdcarray<ShaderDebugState> ContinueDebug();
void ApplyDebugSourceVars(size_t startOffs, ThreadState &thread, ShaderDebugState &state);
Iter GetIterForInstruction(uint32_t inst);
uint32_t GetInstructionForIter(Iter it);
uint32_t GetInstructionForFunction(Id id);
@@ -358,7 +355,6 @@ public:
bool HasDebugInfo() const { return m_DebugInfo.valid; }
bool InDebugScope(uint32_t inst) const;
rdcstr GetHumanName(Id id);
void AddSourceVars(rdcarray<SourceVariableMapping> &sourceVars, const ShaderVariable &var, Id id);
void AllocateVariable(Id id, Id typeId, ShaderVariable &outVar);
ShaderVariable ReadFromPointer(const ShaderVariable &v) const;
@@ -377,7 +373,6 @@ public:
uint32_t GetNumInstructions() { return (uint32_t)instructionOffsets.size(); }
GlobalState GetGlobal() { return global; }
const rdcarray<Id> &GetLiveGlobals() { return liveGlobals; }
const rdcarray<SourceVariableMapping> &GetGlobalSourceVars();
ThreadState &GetActiveLane() { return workgroup[activeLaneIndex]; }
const ThreadState &GetActiveLane() const { return workgroup[activeLaneIndex]; }
private:
@@ -397,6 +392,9 @@ private:
void MakeSignatureNames(const rdcarray<SPIRVInterfaceAccess> &sigList, rdcarray<rdcstr> &sigNames);
void FillDebugSourceVars(rdcarray<InstructionSourceInfo> &instInfo);
void FillDefaultSourceVars(rdcarray<InstructionSourceInfo> &instInfo);
/////////////////////////////////////////////////////////
// debug data
@@ -426,17 +424,16 @@ private:
rdcarray<MemberName> memberNames;
std::map<ShaderEntryPoint, Id> entryLookup;
DenseIdMap<size_t> idDeathOffset;
DenseIdMap<rdcpair<size_t, size_t>> idLiveRange;
SparseIdMap<size_t> m_Files;
LineColumnInfo m_CurLineCol;
std::map<size_t, LineColumnInfo> m_LineColInfo;
rdcarray<InstructionSourceInfo> m_InstInfo;
SparseIdMap<uint32_t> labelInstruction;
// the live mutable global variables, to initialise a stack frame's live list
rdcarray<Id> liveGlobals;
rdcarray<SourceVariableMapping> globalSourceVars;
struct Function
{
@@ -464,6 +461,8 @@ private:
ScopeData *curScope = NULL;
InlineData *curInline = NULL;
rdcarray<LocalMapping> scopelessMappings;
rdcarray<Id> globals;
rdcarray<Id> constants;
@@ -474,7 +473,6 @@ private:
std::map<size_t, ScopeData *> lineScope;
std::map<size_t, InlineData *> lineInline;
std::map<size_t, LocalMapping> localMappings;
rdcarray<LocalMapping> activeLocalMappings;
} m_DebugInfo;
@@ -818,9 +818,10 @@ ShaderDebugTrace *Debugger::BeginDebug(DebugAPIWrapper *api, const ShaderStage s
sourceVar.variables.push_back(DebugVariableReference(
isInput ? DebugVariableType::Input : DebugVariableType::Variable, debugVarName, x));
ret->sourceVars.push_back(sourceVar);
if(!isInput && addSource)
globalSourceVars.push_back(sourceVar);
if(isInput)
ret->sourceVars.push_back(sourceVar);
else if(addSource)
ret->sourceVars.push_back(sourceVar);
}
};
@@ -1206,7 +1207,7 @@ ShaderDebugTrace *Debugger::BeginDebug(DebugAPIWrapper *api, const ShaderStage s
sourceVar.variables.push_back(
DebugVariableReference(DebugVariableType::Variable, var.name, x));
globalSourceVars.push_back(sourceVar);
ret->sourceVars.push_back(sourceVar);
}
}
else
@@ -1236,20 +1237,22 @@ ShaderDebugTrace *Debugger::BeginDebug(DebugAPIWrapper *api, const ShaderStage s
p.Set(*this, global, lane);
}
ret->lineInfo.resize(instructionOffsets.size());
for(size_t i = 0; i < instructionOffsets.size(); i++)
// this contains all the accumulated line number information. Add in our disassembly mapping
ret->instInfo = m_InstInfo;
for(size_t i = 0; i < m_InstInfo.size(); i++)
{
ret->lineInfo[i] = m_LineColInfo[instructionOffsets[i]];
{
auto it = instructionLines.find(instructionOffsets[i]);
if(it != instructionLines.end())
ret->lineInfo[i].disassemblyLine = it->second;
else
ret->lineInfo[i].disassemblyLine = 0;
}
auto it = instructionLines.find(instructionOffsets[m_InstInfo[i].instruction]);
if(it != instructionLines.end())
ret->instInfo[i].lineInfo.disassemblyLine = it->second;
else
ret->instInfo[i].lineInfo.disassemblyLine = 0;
}
if(m_DebugInfo.valid)
FillDebugSourceVars(ret->instInfo);
else
FillDefaultSourceVars(ret->instInfo);
ret->constantBlocks = global.constantBlocks;
ret->readOnlyResources = global.readOnlyResources;
ret->readWriteResources = global.readWriteResources;
@@ -1292,261 +1295,365 @@ ShaderDebugTrace *Debugger::BeginDebug(DebugAPIWrapper *api, const ShaderStage s
return ret;
}
void Debugger::ApplyDebugSourceVars(size_t startOffs, ThreadState &thread, ShaderDebugState &state)
void Debugger::FillDebugSourceVars(rdcarray<InstructionSourceInfo> &instInfo)
{
size_t endOffs = instructionOffsets[thread.nextInstruction - 1];
// apply any local mapping changes
for(auto it = m_DebugInfo.localMappings.lower_bound(startOffs);
it != m_DebugInfo.localMappings.end(); ++it)
for(InstructionSourceInfo &i : instInfo)
{
// if we've gone too far, end
if(it->first > endOffs)
break;
size_t offs = instructionOffsets[i.instruction];
LocalMapping mapping = it->second;
const ScopeData *scope = GetScope(offs);
if(mapping.debugVar == Id())
{
// if the Ids are empty, this is a scope exiting. Potentially multiple scopes at
// once. The only scopes that could have exited are the ones that we were previously
// in, so start from the scope of the previous instruction and walk up its parents.
// Set curId to empty on all the locals of any scope that's exited
const ScopeData *scope = GetScope(startOffs);
while(scope)
{
if(scope->end <= endOffs)
{
m_DebugInfo.activeLocalMappings.removeIf(
[scope](const LocalMapping &l) { return scope->locals.contains(l.sourceVar); });
}
scope = scope->parent;
}
}
else if(thread.ids[mapping.debugVar].type == VarType::Unknown)
{
if(!scope)
continue;
}
else
// track which mappings we've processed, so if the same variable has mappings in multiple scopes
// we only pick the innermost.
rdcarray<LocalMapping> processed;
while(scope)
{
LocalMapping search;
search.sourceVar = mapping.sourceVar;
// find start of this sourceVar
LocalMapping *pos = std::lower_bound(m_DebugInfo.activeLocalMappings.begin(),
m_DebugInfo.activeLocalMappings.end(), search);
// look over all existing local mappings, and if any have been made redundant remove
// them
for(size_t i = (pos - m_DebugInfo.activeLocalMappings.begin());
i < m_DebugInfo.activeLocalMappings.size();)
for(size_t m = 0; m < scope->localMappings.size(); m++)
{
if(m_DebugInfo.activeLocalMappings[i].sourceVar != mapping.sourceVar)
const LocalMapping &mapping = scope->localMappings[m];
// if this mapping is past the current instruction, stop here.
if(mapping.instIndex > i.instruction)
break;
if(mapping.isSourceSupersetOf(m_DebugInfo.activeLocalMappings[i]))
// see if this mapping is superceded by a later mapping that is in scope for this
// instruction. This is a bit inefficient but simple. The alternative would be to do record
// start and end points for each mapping and update the end points, but this is simple and
// should be limited since it's only per-scope
bool supercede = false;
for(size_t n = m + 1; n < scope->localMappings.size(); n++)
{
m_DebugInfo.activeLocalMappings.erase(i);
continue;
}
const LocalMapping &laterMapping = scope->localMappings[n];
i++;
}
// if this mapping is past the current instruction, stop here.
if(laterMapping.instIndex > i.instruction)
break;
mapping.stepIndex = state.stepIndex;
// now add the new mapping in sorted order
pos = std::lower_bound(m_DebugInfo.activeLocalMappings.begin(),
m_DebugInfo.activeLocalMappings.end(), mapping);
m_DebugInfo.activeLocalMappings.insert(pos - m_DebugInfo.activeLocalMappings.begin(), mapping);
}
}
// start with the global source vars
state.sourceVars = globalSourceVars;
const ScopeData *scope = GetScope(endOffs);
// only add locals when in a scope
if(scope)
{
// get the function, only add locals that are in this function or a block child
const ScopeData *func = scope;
while(func && func->parent && func->type != DebugScope::Function)
func = func->parent;
rdcarray<LocalMapping> sorted = m_DebugInfo.activeLocalMappings;
std::sort(sorted.begin(), sorted.end(), [&thread](const LocalMapping &a, const LocalMapping &b) {
size_t aStep = a.stepIndex;
size_t bStep = b.stepIndex;
// declarations use the step index of the last write rather than the step index they
// were added
if(a.isDeclare && thread.lastWrite[a.debugVar] > 0)
aStep = thread.lastWrite[a.debugVar];
if(b.isDeclare && thread.lastWrite[b.debugVar] > 0)
bStep = thread.lastWrite[b.debugVar];
return aStep < bStep;
});
for(const LocalMapping &mapping : sorted)
{
const LocalData &l = m_DebugInfo.locals[mapping.sourceVar];
const ScopeData *lfunc = l.scope;
while(lfunc && lfunc->parent && lfunc->type != DebugScope::Function)
lfunc = lfunc->parent;
if(lfunc != func)
continue;
// if it doesn't have indexes this is simple, set up a 1:1 map
if(mapping.indexes.isEmpty())
{
SourceVariableMapping sourceVar;
const TypeData *typeWalk = l.type;
sourceVar.name = l.name;
sourceVar.offset = 0;
sourceVar.rows = 1U;
sourceVar.columns = 1U;
if(typeWalk->matSize != 0)
{
const TypeData &vec = m_DebugInfo.types[typeWalk->baseType];
const TypeData &scalar = m_DebugInfo.types[vec.baseType];
sourceVar.type = scalar.type;
if(typeWalk->colMajorMat)
{
sourceVar.rows = RDCMAX(1U, vec.vecSize);
sourceVar.columns = RDCMAX(1U, typeWalk->matSize);
}
else
{
sourceVar.rows = RDCMAX(1U, typeWalk->matSize);
sourceVar.columns = RDCMAX(1U, vec.vecSize);
}
}
else if(typeWalk->vecSize != 0)
{
const TypeData &scalar = m_DebugInfo.types[typeWalk->baseType];
sourceVar.type = scalar.type;
sourceVar.columns = RDCMAX(1U, typeWalk->vecSize);
}
else
{
while(typeWalk && typeWalk->baseType != Id() && typeWalk->type == VarType::Unknown)
typeWalk = &m_DebugInfo.types[typeWalk->baseType];
sourceVar.type = typeWalk->type;
if(sourceVar.type == VarType::Unknown)
sourceVar.type = VarType::Struct;
ShaderVariable var = ReadFromPointer(thread.ids[mapping.debugVar]);
if(var.type != VarType::Struct)
{
sourceVar.rows = var.rows;
sourceVar.columns = var.columns;
}
}
for(uint32_t x = 0; x < sourceVar.rows * sourceVar.columns; x++)
sourceVar.variables.push_back(
DebugVariableReference(DebugVariableType::Variable, GetRawName(mapping.debugVar), x));
state.sourceVars.push_back(sourceVar);
}
else
{
SourceVariableMapping sourceVar;
rdcarray<uint32_t> indexes = mapping.indexes;
const TypeData *typeWalk = l.type;
sourceVar.name = l.name;
sourceVar.offset = 0;
sourceVar.rows = 1U;
sourceVar.columns = 1U;
while(!indexes.empty())
{
if(typeWalk->arrayDimension > 0)
{
uint32_t numIdxs = (uint32_t)indexes.size();
for(size_t i = 0; i < RDCMIN(typeWalk->arrayDimension, numIdxs); i++)
{
sourceVar.name += StringFormat::Fmt("[%u]", indexes.back());
indexes.pop_back();
}
typeWalk = &m_DebugInfo.types[typeWalk->baseType];
}
else if(!typeWalk->structMembers.empty())
{
uint32_t idx = indexes.back();
indexes.pop_back();
sourceVar.name += StringFormat::Fmt(".%s", typeWalk->structMembers[idx].first.c_str());
typeWalk = &m_DebugInfo.types[typeWalk->structMembers[idx].second];
}
else
// if this mapping will supercede
if(laterMapping.isSourceSupersetOf(mapping))
{
supercede = true;
break;
}
}
RDCASSERT(indexes.empty());
if(typeWalk->matSize != 0)
for(size_t n = 0; n < processed.size(); n++)
{
const TypeData &vec = m_DebugInfo.types[typeWalk->baseType];
const TypeData &scalar = m_DebugInfo.types[vec.baseType];
sourceVar.type = scalar.type;
if(typeWalk->colMajorMat)
if(processed[n].isSourceSupersetOf(mapping))
{
sourceVar.rows = RDCMAX(1U, vec.vecSize);
sourceVar.columns = RDCMAX(1U, typeWalk->matSize);
supercede = true;
break;
}
}
// don't add the current mapping if it's going to be superceded.
if(supercede)
continue;
const LocalData &l = m_DebugInfo.locals[mapping.sourceVar];
// if it doesn't have indexes this is simple, set up a 1:1 map
if(mapping.indexes.isEmpty())
{
SourceVariableMapping sourceVar;
const TypeData *typeWalk = l.type;
sourceVar.name = l.name;
sourceVar.offset = 0;
sourceVar.rows = 1U;
sourceVar.columns = 1U;
// skip past any pointer types to get the 'real' type that we'll see
while(typeWalk && typeWalk->baseType != Id() && typeWalk->type == VarType::GPUPointer)
typeWalk = &m_DebugInfo.types[typeWalk->baseType];
if(typeWalk->matSize != 0)
{
const TypeData &vec = m_DebugInfo.types[typeWalk->baseType];
const TypeData &scalar = m_DebugInfo.types[vec.baseType];
sourceVar.type = scalar.type;
if(typeWalk->colMajorMat)
{
sourceVar.rows = RDCMAX(1U, vec.vecSize);
sourceVar.columns = RDCMAX(1U, typeWalk->matSize);
}
else
{
sourceVar.rows = RDCMAX(1U, typeWalk->matSize);
sourceVar.columns = RDCMAX(1U, vec.vecSize);
}
}
else if(typeWalk->vecSize != 0)
{
const TypeData &scalar = m_DebugInfo.types[typeWalk->baseType];
sourceVar.type = scalar.type;
sourceVar.columns = RDCMAX(1U, typeWalk->vecSize);
}
else
{
sourceVar.rows = RDCMAX(1U, typeWalk->matSize);
sourceVar.columns = RDCMAX(1U, vec.vecSize);
}
}
else if(typeWalk->vecSize != 0)
{
const TypeData &scalar = m_DebugInfo.types[typeWalk->baseType];
// walk down until we get to a scalar type, if we get there. This means arrays of basic
// types will get the right type
while(typeWalk && typeWalk->baseType != Id() && typeWalk->type == VarType::Unknown)
typeWalk = &m_DebugInfo.types[typeWalk->baseType];
sourceVar.type = scalar.type;
sourceVar.columns = RDCMAX(1U, typeWalk->vecSize);
sourceVar.type = typeWalk->type;
// anything else we treat as a struct
if(sourceVar.type == VarType::Unknown)
sourceVar.type = VarType::Struct;
}
for(uint32_t x = 0; x < sourceVar.rows * sourceVar.columns; x++)
sourceVar.variables.push_back(DebugVariableReference(DebugVariableType::Variable,
GetRawName(mapping.debugVar), x));
i.sourceVars.push_back(sourceVar);
}
else
{
while(typeWalk && typeWalk->baseType != Id() && typeWalk->type == VarType::Unknown)
typeWalk = &m_DebugInfo.types[typeWalk->baseType];
SourceVariableMapping sourceVar;
sourceVar.type = typeWalk->type;
if(sourceVar.type == VarType::Unknown)
sourceVar.type = VarType::Struct;
rdcarray<uint32_t> indexes = mapping.indexes;
const TypeData *typeWalk = l.type;
sourceVar.name = l.name;
sourceVar.offset = 0;
sourceVar.rows = 1U;
sourceVar.columns = 1U;
while(!indexes.empty())
{
if(typeWalk->arrayDimension > 0)
{
uint32_t numIdxs = (uint32_t)indexes.size();
for(size_t a = 0; a < RDCMIN(typeWalk->arrayDimension, numIdxs); a++)
{
sourceVar.name += StringFormat::Fmt("[%u]", indexes.back());
indexes.pop_back();
}
typeWalk = &m_DebugInfo.types[typeWalk->baseType];
}
else if(!typeWalk->structMembers.empty())
{
uint32_t idx = indexes.back();
indexes.pop_back();
sourceVar.name += StringFormat::Fmt(".%s", typeWalk->structMembers[idx].first.c_str());
typeWalk = &m_DebugInfo.types[typeWalk->structMembers[idx].second];
}
else
{
break;
}
}
const char swizzle[] = "xyzw";
if(typeWalk->matSize != 0)
{
const TypeData &vec = m_DebugInfo.types[typeWalk->baseType];
const TypeData &scalar = m_DebugInfo.types[vec.baseType];
sourceVar.type = scalar.type;
if(typeWalk->colMajorMat)
{
sourceVar.rows = RDCMAX(1U, vec.vecSize);
sourceVar.columns = RDCMAX(1U, typeWalk->matSize);
}
else
{
sourceVar.rows = RDCMAX(1U, typeWalk->matSize);
sourceVar.columns = RDCMAX(1U, vec.vecSize);
}
// two remaining indices selects a scalar within the matrix
if(indexes.size() == 2)
{
uint32_t col = indexes[0];
uint32_t row = indexes[1];
RDCASSERT(col < 4 && row < 4, col, row);
sourceVar.name += StringFormat::Fmt(".row%u.%c", row, swizzle[RDCMIN(col, 3U)]);
sourceVar.variables.push_back(DebugVariableReference(
DebugVariableType::Variable, GetRawName(mapping.debugVar), indexes[0]));
}
// one remaining index selects a column within the matrix. Since we display source vars
// as row-major, this means adding 4 mappings
else if(indexes.size() == 1)
{
uint32_t col = indexes[0];
rdcstr name = sourceVar.name;
for(uint32_t row = 0; row < sourceVar.rows; row++)
{
sourceVar.name = name + StringFormat::Fmt(".row%u.%c", row, swizzle[RDCMIN(col, 3U)]);
sourceVar.variables.push_back(DebugVariableReference(
DebugVariableType::Variable, GetRawName(mapping.debugVar), row));
}
}
else
{
RDCASSERT(indexes.empty(), indexes.size());
for(uint32_t x = 0; x < sourceVar.rows * sourceVar.columns; x++)
sourceVar.variables.push_back(DebugVariableReference(
DebugVariableType::Variable, GetRawName(mapping.debugVar), x));
}
}
else if(typeWalk->vecSize != 0)
{
const TypeData &scalar = m_DebugInfo.types[typeWalk->baseType];
sourceVar.type = scalar.type;
sourceVar.columns = RDCMAX(1U, typeWalk->vecSize);
// remaining index selects a scalar within the vector
if(indexes.size() == 1)
{
RDCASSERT(indexes[0] < 4, indexes[0]);
sourceVar.name += StringFormat::Fmt(".%c", swizzle[RDCMIN(indexes[0], 3U)]);
sourceVar.variables.push_back(DebugVariableReference(
DebugVariableType::Variable, GetRawName(mapping.debugVar), 0));
}
else
{
RDCASSERT(indexes.empty(), indexes.size());
for(uint32_t x = 0; x < sourceVar.rows * sourceVar.columns; x++)
sourceVar.variables.push_back(DebugVariableReference(
DebugVariableType::Variable, GetRawName(mapping.debugVar), x));
}
}
else
{
// walk down until we get to a scalar type, if we get there. This means arrays of basic
// types will get the right type
while(typeWalk && typeWalk->baseType != Id() && typeWalk->type == VarType::Unknown)
typeWalk = &m_DebugInfo.types[typeWalk->baseType];
sourceVar.type = typeWalk->type;
// anything else we treat as a struct
if(sourceVar.type == VarType::Unknown)
sourceVar.type = VarType::Struct;
sourceVar.variables.push_back(DebugVariableReference(DebugVariableType::Variable,
GetRawName(mapping.debugVar), 0));
}
i.sourceVars.push_back(sourceVar);
}
processed.push_back(mapping);
}
// if we reach a function scope, don't go up any further.
if(scope->type == DebugScope::Function)
break;
// move to the parent scope and apply the mappings there
scope = scope->parent;
}
}
}
void Debugger::FillDefaultSourceVars(rdcarray<InstructionSourceInfo> &instInfo)
{
rdcarray<SourceVariableMapping> sourceVars;
rdcarray<Id> debugVars;
for(InstructionSourceInfo &i : instInfo)
{
// the source vars for this instruction are whatever we have currently, because when we're
// looking up the source vars for instruction X we are effectively talking abotu the state just
// before X executes, not just after.
i.sourceVars = sourceVars;
// now update the sourcevars for after this instruction executed
size_t offs = instructionOffsets[i.instruction];
Iter it(m_SPIRV, offs);
OpDecoder opdata(it);
Id id = opdata.result;
// stores can bring their pointer into being, if it's the first write.
if(opdata.op == Op::Store)
id = OpStore(it).pointer;
// if this is the offset where the id's live range begins, try to add the source name for it if
// one exists.
if(id != Id() && idLiveRange[id].first == offs)
{
rdcstr name;
auto dyn = dynamicNames.find(id);
if(dyn != dynamicNames.end())
name = dyn->second;
else
name = strings[id];
if(!name.empty())
{
SourceVariableMapping sourceVar;
const DataType *type = &GetTypeForId(id);
while(type->type == DataType::PointerType || type->type == DataType::ArrayType)
type = &GetType(type->InnerType());
sourceVar.name = name;
sourceVar.offset = 0;
if(type->type == DataType::MatrixType || type->type == DataType::VectorType ||
type->type == DataType::ScalarType)
sourceVar.type = type->scalar().Type();
else if(type->type == DataType::StructType)
sourceVar.type = VarType::Struct;
else if(type->type == DataType::ImageType || type->type == DataType::SampledImageType ||
type->type == DataType::SamplerType)
sourceVar.type = VarType::ReadOnlyResource;
sourceVar.rows = RDCMAX(1U, (uint32_t)type->matrix().count);
sourceVar.columns = RDCMAX(1U, (uint32_t)type->vector().count);
rdcstr rawName = GetRawName(id);
for(uint32_t x = 0; x < sourceVar.rows * sourceVar.columns; x++)
sourceVar.variables.push_back(
DebugVariableReference(DebugVariableType::Variable, GetRawName(mapping.debugVar), x));
DebugVariableReference(DebugVariableType::Variable, rawName, x));
state.sourceVars.push_back(sourceVar);
sourceVars.push_back(sourceVar);
debugVars.push_back(id);
}
}
// see which vars have expired
for(size_t d = 0; d < debugVars.size();)
{
if(offs > idLiveRange[debugVars[d]].second)
{
sourceVars.erase(d);
debugVars.erase(d);
continue;
}
d++;
}
// all variables/IDs are function-local
if(opdata.op == Op::FunctionEnd)
{
sourceVars.clear();
debugVars.clear();
}
}
}
@@ -1569,15 +1676,9 @@ rdcarray<ShaderDebugState> Debugger::ContinueDebug()
if(lane == activeLaneIndex)
{
size_t beginOffs = instructionOffsets[thread.nextInstruction];
thread.EnterEntryPoint(&initial);
thread.FillCallstack(initial);
initial.nextInstruction = thread.nextInstruction;
initial.sourceVars = thread.sourceVars;
if(m_DebugInfo.valid)
ApplyDebugSourceVars(beginOffs, thread, initial);
}
else
{
@@ -1645,19 +1746,13 @@ rdcarray<ShaderDebugState> Debugger::ContinueDebug()
for(size_t l = 0; l < thread.live.size();)
{
Id id = thread.live[l];
if(idDeathOffset[id] < instOffs)
if(idLiveRange[id].second < instOffs)
{
thread.live.erase(l);
ShaderVariableChange change;
change.before = GetPointerValue(thread.ids[id]);
state.changes.push_back(change);
rdcstr name = GetRawName(id);
thread.sourceVars.removeIf([name](const SourceVariableMapping &var) {
return var.variables[0].name.beginsWith(name);
});
continue;
}
@@ -1684,8 +1779,6 @@ rdcarray<ShaderDebugState> Debugger::ContinueDebug()
if(m_DebugInfo.valid)
{
ApplyDebugSourceVars(instOffs, thread, state);
size_t endOffs = instructionOffsets[thread.nextInstruction - 1];
// append any inlined functions to the top of the stack
@@ -1719,19 +1812,6 @@ rdcarray<ShaderDebugState> Debugger::ContinueDebug()
inlined = inlined->parent;
}
}
else
{
state.sourceVars = thread.sourceVars;
// sort sourceVars by last write to the underlying variable
std::sort(state.sourceVars.begin(), state.sourceVars.end(),
[&thread](const SourceVariableMapping &a, const SourceVariableMapping &b) {
Id aId = ParseRawName(a.variables[0].name);
Id bId = ParseRawName(b.variables[0].name);
return thread.lastWrite[aId] < thread.lastWrite[bId];
});
}
ret.push_back(std::move(state));
@@ -2336,42 +2416,6 @@ rdcstr Debugger::GetHumanName(Id id)
return name;
}
const rdcarray<SourceVariableMapping> &Debugger::GetGlobalSourceVars()
{
static const rdcarray<SourceVariableMapping> empty;
return m_DebugInfo.valid ? empty : globalSourceVars;
}
void Debugger::AddSourceVars(rdcarray<SourceVariableMapping> &sourceVars, const ShaderVariable &var,
Id id)
{
if(m_DebugInfo.valid)
return;
rdcstr name;
auto it = dynamicNames.find(id);
if(it != dynamicNames.end())
name = it->second;
else
name = strings[id];
if(!name.empty())
{
SourceVariableMapping sourceVar;
sourceVar.name = name;
sourceVar.offset = 0;
sourceVar.type = var.type;
sourceVar.rows = RDCMAX(1U, (uint32_t)var.rows);
sourceVar.columns = RDCMAX(1U, (uint32_t)var.columns);
for(uint32_t x = 0; x < sourceVar.rows * sourceVar.columns; x++)
sourceVar.variables.push_back(DebugVariableReference(DebugVariableType::Variable, var.name, x));
sourceVars.push_back(sourceVar);
}
}
void Debugger::CalcActiveMask(rdcarray<bool> &activeMask)
{
// one bool per workgroup thread
@@ -2827,7 +2871,9 @@ void Debugger::PreParse(uint32_t maxId)
Processor::PreParse(maxId);
strings.resize(idTypes.size());
idDeathOffset.resize(idTypes.size());
idLiveRange.resize(idTypes.size());
m_InstInfo.reserve(idTypes.size());
}
void Debugger::PostParse()
@@ -2839,40 +2885,35 @@ void Debugger::PostParse()
// global IDs never hit a death point
for(const Variable &v : globals)
idDeathOffset[v.id] = ~0U;
idLiveRange[v.id].second = ~0U;
if(m_DebugInfo.valid)
{
// every scope's parent lasts at least as long as it
for(auto it = m_DebugInfo.scopes.begin(); it != m_DebugInfo.scopes.end(); ++it)
{
ScopeData *scope = &it->second;
// keep every ID referenced by a local alive until the scope ends. We do this even if a source
// variable maps to multiple debug variables and technically the earlier ones could be left to
// die when superceeded by the later ones. This is simple and only means a little bloating of
// debug variables in the UI (which generally won't be viewed directly anyway)
for(LocalMapping &m : scope->localMappings)
{
Id id = m.debugVar;
if(id == Id())
continue;
idLiveRange[id].second = RDCMAX(scope->end + 1, idLiveRange[id].second);
}
// every scope's parent lasts at least as long as it
while(scope->parent)
{
scope->parent->end = RDCMAX(scope->parent->end, scope->end);
scope = scope->parent;
}
}
// add a dummy localMappings entry for each scope end
for(auto it = m_DebugInfo.scopes.begin(); it != m_DebugInfo.scopes.end(); ++it)
m_DebugInfo.localMappings[it->second.end] = {0, Id(), Id()};
for(auto it = m_DebugInfo.localMappings.begin(); it != m_DebugInfo.localMappings.end(); ++it)
{
if(it->second.debugVar == Id())
continue;
const LocalData &l = m_DebugInfo.locals[it->second.sourceVar];
if(l.scope == NULL)
continue;
// keep last raw ID alive until the scope ends
Id id = it->second.debugVar;
idDeathOffset[id] = RDCMAX(l.scope->end + 1, RDCMAX(it->first + 1, idDeathOffset[id]));
}
}
memberNames.clear();
@@ -2888,10 +2929,15 @@ void Debugger::RegisterOp(Iter it)
// since blocks always end with a terminator that doesn't consume IDs we're interested in
// (variables) we'll always have one extra instruction to step to
OpDecoder::ForEachID(it, [this, &it](Id id, bool result) {
idDeathOffset[id] = RDCMAX(it.offs() + 1, idDeathOffset[id]);
if(result)
idLiveRange[id].first = it.offs();
idLiveRange[id].second = RDCMAX(it.offs() + 1, idLiveRange[id].second);
});
bool leaveScope = false;
bool executable = curFunction != NULL;
const uint32_t curInstIndex = (uint32_t)instructionOffsets.size();
if(opdata.op == Op::ExtInst)
{
@@ -2903,7 +2949,7 @@ void Debugger::RegisterOp(Iter it)
for(const uint32_t param : extinst.params)
{
Id id = Id::fromWord(param);
idDeathOffset[id] = RDCMAX(it.offs() + 1, idDeathOffset[id]);
idLiveRange[id].second = RDCMAX(it.offs() + 1, idLiveRange[id].second);
}
}
else if(knownExtSet[ExtSet_Printf] == extinst.set)
@@ -2912,7 +2958,7 @@ void Debugger::RegisterOp(Iter it)
for(const uint32_t param : extinst.params)
{
Id id = Id::fromWord(param);
idDeathOffset[id] = RDCMAX(it.offs() + 1, idDeathOffset[id]);
idLiveRange[id].second = RDCMAX(it.offs() + 1, idLiveRange[id].second);
}
}
else if(knownExtSet[ExtSet_ShaderDbg] == extinst.set)
@@ -2920,6 +2966,9 @@ void Debugger::RegisterOp(Iter it)
// the types are identical just with different accessors
OpShaderDbg &dbg = (OpShaderDbg &)extinst;
if(dbg.inst != ShaderDbg::Value)
executable = false;
switch(dbg.inst)
{
case ShaderDbg::Source:
@@ -3004,6 +3053,7 @@ void Debugger::RegisterOp(Iter it)
case ShaderDbg::TypePointer:
{
m_DebugInfo.types[dbg.result].baseType = dbg.arg<Id>(0);
m_DebugInfo.types[dbg.result].type = VarType::GPUPointer;
break;
}
case ShaderDbg::TypeVector:
@@ -3088,6 +3138,9 @@ void Debugger::RegisterOp(Iter it)
m_DebugInfo.curScope = &m_DebugInfo.scopes[dbg.arg<Id>(0)];
m_DebugInfo.curScope->localMappings.append(std::move(m_DebugInfo.scopelessMappings));
m_DebugInfo.scopelessMappings.clear();
if(dbg.params.size() >= 2)
m_DebugInfo.curInline = &m_DebugInfo.inlined[dbg.arg<Id>(1)];
else
@@ -3131,14 +3184,19 @@ void Debugger::RegisterOp(Iter it)
case ShaderDbg::Declare:
case ShaderDbg::Value:
{
Id id = dbg.arg<Id>(1);
Id sourceVarId = dbg.arg<Id>(0);
Id debugVarId = dbg.arg<Id>(1);
LocalMapping &mapping = m_DebugInfo.localMappings[it.offs()];
rdcarray<LocalMapping> &mappings = m_DebugInfo.curScope
? m_DebugInfo.curScope->localMappings
: m_DebugInfo.scopelessMappings;
mapping = {0, dbg.arg<Id>(0), id, dbg.inst == ShaderDbg::Declare};
mappings.push_back({curInstIndex, sourceVarId, debugVarId, dbg.inst == ShaderDbg::Declare});
LocalMapping &mapping = mappings.back();
if(constants.find(id) != constants.end() && !m_DebugInfo.constants.contains(id))
m_DebugInfo.constants.push_back(id);
if(constants.find(debugVarId) != constants.end() &&
!m_DebugInfo.constants.contains(debugVarId))
m_DebugInfo.constants.push_back(debugVarId);
mapping.indexes.resize(dbg.params.size() - 3);
for(uint32_t i = 0; i < mapping.indexes.size(); i++)
@@ -3162,6 +3220,7 @@ void Debugger::RegisterOp(Iter it)
}
}
}
break;
}
case ShaderDbg::InlinedAt:
@@ -3243,18 +3302,20 @@ void Debugger::RegisterOp(Iter it)
if(!m_DebugInfo.valid)
m_CurLineCol = LineColumnInfo();
}
else
else if(executable)
{
// for debug info, only apply line info if we're in a scope. Otherwise the line info may not
// apply to this instruction. This means OpPhi's will never be line mapped
if(m_DebugInfo.valid)
{
if(m_DebugInfo.curScope)
m_LineColInfo[it.offs()] = m_CurLineCol;
m_InstInfo.push_back({curInstIndex, m_CurLineCol});
else
m_InstInfo.push_back({curInstIndex, LineColumnInfo()});
}
else
{
m_LineColInfo[it.offs()] = m_CurLineCol;
m_InstInfo.push_back({curInstIndex, m_CurLineCol});
}
}
@@ -3354,13 +3415,11 @@ void Debugger::RegisterOp(Iter it)
if(opdata.op == Op::FunctionEnd)
{
// don't automatically kill function parameters and variables. They will be manually killed when
// returning from a function's scope
// allow function parameters and variables to live indefinitely
for(const Id &id : curFunction->parameters)
idDeathOffset[id] = ~0U;
idLiveRange[id].second = ~0U;
for(const Id &id : curFunction->variables)
idDeathOffset[id] = ~0U;
idLiveRange[id].second = ~0U;
curFunction = NULL;
}
}
+12 -3
View File
@@ -360,6 +360,16 @@ void DoSerialise(SerialiserType &ser, LineColumnInfo &el)
SIZE_CHECK(24);
}
template <typename SerialiserType>
void DoSerialise(SerialiserType &ser, InstructionSourceInfo &el)
{
SERIALISE_MEMBER(instruction);
SERIALISE_MEMBER(lineInfo);
SERIALISE_MEMBER(sourceVars);
SIZE_CHECK(56);
}
template <typename SerialiserType>
void DoSerialise(SerialiserType &ser, ShaderVariableChange &el)
{
@@ -376,10 +386,9 @@ void DoSerialise(SerialiserType &ser, ShaderDebugState &el)
SERIALISE_MEMBER(stepIndex);
SERIALISE_MEMBER(flags);
SERIALISE_MEMBER(changes);
SERIALISE_MEMBER(sourceVars);
SERIALISE_MEMBER(callstack);
SIZE_CHECK(88);
SIZE_CHECK(64);
}
template <typename SerialiserType>
@@ -392,7 +401,7 @@ void DoSerialise(SerialiserType &ser, ShaderDebugTrace &el)
SERIALISE_MEMBER(readOnlyResources);
SERIALISE_MEMBER(readWriteResources);
SERIALISE_MEMBER(sourceVars);
SERIALISE_MEMBER(lineInfo);
SERIALISE_MEMBER(instInfo);
// serialise the debugger pointer entirely opaquely, this is only used for replay proxying
uint64_t debugger = 0;