From 935fa49c984f4131fff0223b47ef87c8ad57041b Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 13 Jul 2022 16:59:32 +0100 Subject: [PATCH] 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. --- qrenderdoc/Code/pyrenderdoc/renderdoc.i | 1 + qrenderdoc/Windows/ShaderViewer.cpp | 143 +++- qrenderdoc/Windows/ShaderViewer.h | 6 + renderdoc/api/replay/shader_types.h | 65 +- .../driver/shaders/dxbc/dxbc_container.cpp | 36 +- .../driver/shaders/dxbc/dxbc_container.h | 1 - renderdoc/driver/shaders/dxbc/dxbc_debug.cpp | 6 +- renderdoc/driver/shaders/dxbc/dxbc_debug.h | 1 + .../driver/shaders/spirv/spirv_debug.cpp | 6 - renderdoc/driver/shaders/spirv/spirv_debug.h | 76 +- .../shaders/spirv/spirv_debug_setup.cpp | 735 ++++++++++-------- renderdoc/replay/renderdoc_serialise.inl | 15 +- 12 files changed, 622 insertions(+), 469 deletions(-) diff --git a/qrenderdoc/Code/pyrenderdoc/renderdoc.i b/qrenderdoc/Code/pyrenderdoc/renderdoc.i index c6b791ab6..67831228e 100644 --- a/qrenderdoc/Code/pyrenderdoc/renderdoc.i +++ b/qrenderdoc/Code/pyrenderdoc/renderdoc.i @@ -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) diff --git a/qrenderdoc/Windows/ShaderViewer.cpp b/qrenderdoc/Windows/ShaderViewer.cpp index b0ddb3553..f1893e67c 100644 --- a/qrenderdoc/Windows/ShaderViewer.cpp +++ b/qrenderdoc/Windows/ShaderViewer.cpp @@ -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 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 insts = {runToInstruction}; @@ -2275,7 +2303,7 @@ void ShaderViewer::runTo(const rdcarray &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 &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 &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 &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 { diff --git a/qrenderdoc/Windows/ShaderViewer.h b/qrenderdoc/Windows/ShaderViewer.h index 08159dd5c..6402d12f6 100644 --- a/qrenderdoc/Windows/ShaderViewer.h +++ b/qrenderdoc/Windows/ShaderViewer.h @@ -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, diff --git a/renderdoc/api/replay/shader_types.h b/renderdoc/api/replay/shader_types.h index d9994dbc0..6412b8d6b 100644 --- a/renderdoc/api/replay/shader_types.h +++ b/renderdoc/api/replay/shader_types.h @@ -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 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 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 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 lineInfo; + rdcarray instInfo; }; DECLARE_REFLECTION_STRUCT(ShaderDebugTrace); diff --git a/renderdoc/driver/shaders/dxbc/dxbc_container.cpp b/renderdoc/driver/shaders/dxbc/dxbc_container.cpp index 29bb1697b..3f74ff94b 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_container.cpp +++ b/renderdoc/driver/shaders/dxbc/dxbc_container.cpp @@ -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(); diff --git a/renderdoc/driver/shaders/dxbc/dxbc_container.h b/renderdoc/driver/shaders/dxbc/dxbc_container.h index db0d6638c..206489756 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_container.h +++ b/renderdoc/driver/shaders/dxbc/dxbc_container.h @@ -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); diff --git a/renderdoc/driver/shaders/dxbc/dxbc_debug.cpp b/renderdoc/driver/shaders/dxbc/dxbc_debug.cpp index 14ea3d867..d734a5255 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_debug.cpp +++ b/renderdoc/driver/shaders/dxbc/dxbc_debug.cpp @@ -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 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 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++; diff --git a/renderdoc/driver/shaders/dxbc/dxbc_debug.h b/renderdoc/driver/shaders/dxbc/dxbc_debug.h index adefbb2ce..4b175b489 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_debug.h +++ b/renderdoc/driver/shaders/dxbc/dxbc_debug.h @@ -323,6 +323,7 @@ private: const DXBC::Reflection *reflection; const DXBCBytecode::Program *program; + const DXBC::IDebugInfo *debug; rdcarray m_accessedSRVs; rdcarray m_accessedUAVs; diff --git a/renderdoc/driver/shaders/spirv/spirv_debug.cpp b/renderdoc/driver/shaders/spirv/spirv_debug.cpp index 73afe1a94..6605388be 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_debug.cpp @@ -125,12 +125,10 @@ void ThreadState::EnterFunction(const rdcarray &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 // restore the live list from the calling frame live = callstack.back()->live; - sourceVars = callstack.back()->sourceVars; } delete exitingFrame; diff --git a/renderdoc/driver/shaders/spirv/spirv_debug.h b/renderdoc/driver/shaders/spirv/spirv_debug.h index 6d5392435..9fd9f0fd2 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug.h +++ b/renderdoc/driver/shaders/spirv/spirv_debug.h @@ -148,7 +148,6 @@ struct StackFrame // the thread's live list before the function was entered rdcarray live; - rdcarray sourceVars; // the last block we were in and the current block, for OpPhis Id lastBlock, curBlock; @@ -219,8 +218,6 @@ struct ThreadState std::map lastWrite; - rdcarray sourceVars; - // index in the pixel quad uint32_t workgroupIndex; bool helperInvocation; @@ -261,33 +258,6 @@ struct TypeData rdcarray> structMembers; }; -struct ScopeData -{ - DebugScope type; - ScopeData *parent; - uint32_t line; - uint32_t column; - int32_t fileIndex; - size_t end; - - rdcstr name; - - rdcarray 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 indexes; }; +struct ScopeData +{ + DebugScope type; + ScopeData *parent; + uint32_t line; + uint32_t column; + int32_t fileIndex; + size_t end; + + rdcstr name; + + rdcarray locals; + + rdcarray 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 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 &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 &GetLiveGlobals() { return liveGlobals; } - const rdcarray &GetGlobalSourceVars(); ThreadState &GetActiveLane() { return workgroup[activeLaneIndex]; } const ThreadState &GetActiveLane() const { return workgroup[activeLaneIndex]; } private: @@ -397,6 +392,9 @@ private: void MakeSignatureNames(const rdcarray &sigList, rdcarray &sigNames); + void FillDebugSourceVars(rdcarray &instInfo); + void FillDefaultSourceVars(rdcarray &instInfo); + ///////////////////////////////////////////////////////// // debug data @@ -426,17 +424,16 @@ private: rdcarray memberNames; std::map entryLookup; - DenseIdMap idDeathOffset; + DenseIdMap> idLiveRange; SparseIdMap m_Files; LineColumnInfo m_CurLineCol; - std::map m_LineColInfo; + rdcarray m_InstInfo; SparseIdMap labelInstruction; // the live mutable global variables, to initialise a stack frame's live list rdcarray liveGlobals; - rdcarray globalSourceVars; struct Function { @@ -464,6 +461,8 @@ private: ScopeData *curScope = NULL; InlineData *curInline = NULL; + rdcarray scopelessMappings; + rdcarray globals; rdcarray constants; @@ -474,7 +473,6 @@ private: std::map lineScope; std::map lineInline; - std::map localMappings; rdcarray activeLocalMappings; } m_DebugInfo; diff --git a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp index da1336613..6d4de6c55 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp @@ -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 &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 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 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 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 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 &instInfo) +{ + rdcarray sourceVars; + rdcarray 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 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 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 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 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 &Debugger::GetGlobalSourceVars() -{ - static const rdcarray empty; - return m_DebugInfo.valid ? empty : globalSourceVars; -} - -void Debugger::AddSourceVars(rdcarray &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 &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(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(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(1)]; else @@ -3131,14 +3184,19 @@ void Debugger::RegisterOp(Iter it) case ShaderDbg::Declare: case ShaderDbg::Value: { - Id id = dbg.arg(1); + Id sourceVarId = dbg.arg(0); + Id debugVarId = dbg.arg(1); - LocalMapping &mapping = m_DebugInfo.localMappings[it.offs()]; + rdcarray &mappings = m_DebugInfo.curScope + ? m_DebugInfo.curScope->localMappings + : m_DebugInfo.scopelessMappings; - mapping = {0, dbg.arg(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; } } diff --git a/renderdoc/replay/renderdoc_serialise.inl b/renderdoc/replay/renderdoc_serialise.inl index 698a1cac6..90e51fa74 100644 --- a/renderdoc/replay/renderdoc_serialise.inl +++ b/renderdoc/replay/renderdoc_serialise.inl @@ -360,6 +360,16 @@ void DoSerialise(SerialiserType &ser, LineColumnInfo &el) SIZE_CHECK(24); } +template +void DoSerialise(SerialiserType &ser, InstructionSourceInfo &el) +{ + SERIALISE_MEMBER(instruction); + SERIALISE_MEMBER(lineInfo); + SERIALISE_MEMBER(sourceVars); + + SIZE_CHECK(56); +} + template 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 @@ -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;