From 3f8df6b589299bd4b20e18d56b032a422f013385 Mon Sep 17 00:00:00 2001 From: Jake Turner Date: Wed, 14 May 2025 16:28:10 +0100 Subject: [PATCH] Spirv Debugger GroupShared simulation changes On the active thread * GSM reads come from the local GSM cache (not the global GSM data) * GSM writes go to the local GSM cache and the global GSM data * Workgroup Memory barrier populates the local GSM cache with the data from the global GSM data On the non-active threads: * GSM reads/write always operate on the global GSM data * Workgroup Memory barrier is ignored --- .../driver/shaders/spirv/spirv_debug.cpp | 90 +++++++++++++++++-- renderdoc/driver/shaders/spirv/spirv_debug.h | 9 ++ .../shaders/spirv/spirv_debug_setup.cpp | 55 +++++++++--- 3 files changed, 137 insertions(+), 17 deletions(-) diff --git a/renderdoc/driver/shaders/spirv/spirv_debug.cpp b/renderdoc/driver/shaders/spirv/spirv_debug.cpp index 3475d8bb2..a693d65a8 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_debug.cpp @@ -425,6 +425,11 @@ void ThreadState::WritePointerValue(Id pointer, const ShaderVariable &val) for(size_t i = 0; i < pointers.size(); i++) lastWrite[pointers[i]] = m_State ? m_State->stepIndex : nextInstruction; + + // For GSM memory update the global data as well as the local cache, do not send the changes to the UI + auto gsmPtrIt = gsmPointers.find(pointer); + if(gsmPtrIt != gsmPointers.end()) + debugger.WriteThroughPointer(gsmPtrIt->second, val); } } @@ -850,8 +855,20 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray for(Id id : chain.indexes) indices.push_back(uintComp(GetSrc(id), 0)); - SetDst(chain.result, debugger.MakeCompositePointer( - ids[chain.base], debugger.GetPointerBaseId(ids[chain.base]), indices)); + Id baseId = debugger.GetPointerBaseId(ids[chain.base]); + SetDst(chain.result, debugger.MakeCompositePointer(ids[chain.base], baseId, indices)); + + // create duplicate GSM pointers for the active thread which point to the global GSM not the local GSM cache + if(m_State) + { + auto gsmPtrIt = gsmPointers.find(chain.base); + if(gsmPtrIt != gsmPointers.end()) + { + ShaderVariable gsmGlobal = debugger.MakeCompositePointer(gsmPtrIt->second, baseId, indices); + gsmGlobal.name = GetRawName(chain.result); + gsmPointers[chain.result] = gsmGlobal; + } + } break; } case Op::PtrAccessChain: @@ -870,10 +887,26 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray int32_t element = intComp(GetSrc(chain.element), 0); // adjust the address by the element. We should have the array stride since the base pointer // must point into an array and we can't go outside it. - base.SetTypedPointer(val.pointer + element * debugger.GetPointerArrayStride(base), val.shader, - val.pointerTypeID); - SetDst(chain.result, - debugger.MakeCompositePointer(base, debugger.GetPointerBaseId(base), indices)); + uint64_t byteOffset = element * debugger.GetPointerArrayStride(base); + base.SetTypedPointer(val.pointer + byteOffset, val.shader, val.pointerTypeID); + Id baseId = debugger.GetPointerBaseId(ids[chain.base]); + SetDst(chain.result, debugger.MakeCompositePointer(base, baseId, indices)); + + // create duplicate GSM pointers for the active thread which point to the global GSM not the local GSM cache + if(m_State) + { + auto gsmPtrIt = gsmPointers.find(chain.base); + if(gsmPtrIt != gsmPointers.end()) + { + ShaderVariable gsmBase = gsmPtrIt->second; + PointerVal gsmVal = gsmBase.GetPointer(); + gsmBase.SetTypedPointer(gsmVal.pointer + byteOffset, gsmVal.shader, gsmVal.pointerTypeID); + + ShaderVariable gsmGlobal = debugger.MakeCompositePointer(gsmBase, baseId, indices); + gsmGlobal.name = GetRawName(chain.result); + gsmPointers[chain.result] = gsmGlobal; + } + } break; } case Op::ArrayLength: @@ -3780,11 +3813,14 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray case Op::MemoryBarrier: { - // do nothing for now + OpMemoryBarrier barrier(it); + ExecuteMemoryBarrier(barrier.semantics); break; } case Op::ControlBarrier: { + OpControlBarrier barrier(it); + ExecuteMemoryBarrier(barrier.semantics); // For thread barriers the threads must be converged RDCASSERT(!WorkgroupIsDiverged(workgroup)); break; @@ -4935,4 +4971,44 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray m_State = NULL; } +void ThreadState::ExecuteMemoryBarrier(Id semanticsId) +{ + // ignore if not the acitve thread + if(!m_State) + return; + + ShaderVariable var = GetSrc(semanticsId); + MemorySemantics semantics = (MemorySemantics)var.value.u32v[0]; + // only workgroup memory barriers are supported + if(!(semantics & MemorySemantics::WorkgroupMemory)) + return; + + // copy the global GSM memory into the local GSM cache + for(const GSMIndex &gsmIndex : gsmIndexes) + { + const int32_t globalIndex = gsmIndex.global; + const int32_t localIndex = gsmIndex.local; + if(globalIndex < global.workgroups.count()) + { + if(localIndex < privates.count()) + { + ShaderVariableChange change; + const ShaderVariable &globalData = global.workgroups[globalIndex]; + change.before = privates[localIndex]; + AssignValue(privates[localIndex], globalData); + change.after = privates[localIndex]; + if(!(change.after == change.before)) + m_State->changes.push_back(change); + } + else + { + RDCERR("Invalid GSM local index %u MAX %u", localIndex, privates.count()); + } + } + else + { + RDCERR("Invalid GSM index %u MAX %u", globalIndex, global.workgroups.count()); + } + } +} }; // namespace rdcspv diff --git a/renderdoc/driver/shaders/spirv/spirv_debug.h b/renderdoc/driver/shaders/spirv/spirv_debug.h index bbb45ab5f..3a001609a 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug.h +++ b/renderdoc/driver/shaders/spirv/spirv_debug.h @@ -225,6 +225,14 @@ struct ThreadState // changes (and vice-versa - a change via any of those pointers must update all other pointers). SparseIdMap> pointersForId; + SparseIdMap gsmPointers; + struct GSMIndex + { + int32_t global; + int32_t local; + }; + rdcarray gsmIndexes; + // the id of the merge block that the last branch targetted Id mergeBlock; uint32_t convergenceInstruction; @@ -270,6 +278,7 @@ private: void SkipIgnoredInstructions(); void SetConvergencePoint(Id block); + void ExecuteMemoryBarrier(Id semanticsId); static bool WorkgroupIsDiverged(const rdcarray &workgroup); ShaderDebugState *m_State = NULL; diff --git a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp index 236a7ecc7..3d52cddc1 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp @@ -1006,30 +1006,48 @@ ShaderDebugTrace *Debugger::BeginDebug(DebugAPIWrapper *api, const ShaderStage s struct PointerId { PointerId(Id i, rdcarray GlobalState::*th, rdcarray &storage) - : id(i), globalStorage(th), index(storage.size() - 1) + : id(i), globalStorage(th), globalIndex(storage.size() - 1) { } PointerId(Id i, rdcarray ThreadState::*th, rdcarray &storage) - : id(i), threadStorage(th), index(storage.size() - 1) + : id(i), threadStorage(th), threadIndex(storage.size() - 1) + { + } + PointerId(Id i, rdcarray GlobalState::*global, + rdcarray &globalVars, rdcarray ThreadState::*thread, + rdcarray &threadVars) + : id(i), + globalStorage(global), + globalIndex(globalVars.size() - 1), + threadStorage(thread), + threadIndex(threadVars.size() - 1) { } - void Set(Debugger &d, const GlobalState &global, ThreadState &lane) const + void Set(Debugger &d, const GlobalState &global, ThreadState &lane, bool forceLocalGSM) const { - if(globalStorage) - lane.ids[id] = d.MakePointerVariable(id, &(global.*globalStorage)[index]); + const bool isGlobal = (globalIndex != UINT_MAX); + const bool isGSM = isGlobal && (threadIndex != UINT_MAX); + const bool useLocal = (forceLocalGSM && isGSM) || !isGlobal; + + if(!useLocal) + lane.ids[id] = d.MakePointerVariable(id, &(global.*globalStorage)[globalIndex]); else - lane.ids[id] = d.MakePointerVariable(id, &(lane.*threadStorage)[index]); + lane.ids[id] = d.MakePointerVariable(id, &(lane.*threadStorage)[threadIndex]); } Id id; rdcarray GlobalState::*globalStorage = NULL; rdcarray ThreadState::*threadStorage = NULL; - size_t index; + size_t globalIndex = UINT_MAX; + size_t threadIndex = UINT_MAX; }; #define GLOBAL_POINTER(id, list) PointerId(id, &GlobalState::list, global.list) #define THREAD_POINTER(id, list) PointerId(id, &ThreadState::list, active.list) +#define GSM_POINTER(id, globalList, threadList) \ + PointerId(id, &GlobalState::globalList, global.globalList, &ThreadState::threadList, \ + active.threadList) rdcarray pointerIDs; @@ -1540,8 +1558,10 @@ ShaderDebugTrace *Debugger::BeginDebug(DebugAPIWrapper *api, const ShaderStage s } else if(v.storage == StorageClass::Workgroup) { + active.gsmIndexes.push_back({global.workgroups.count(), active.privates.count()}); + active.privates.push_back(var); global.workgroups.push_back(var); - pointerIDs.push_back(GLOBAL_POINTER(v.id, workgroups)); + pointerIDs.push_back(GSM_POINTER(v.id, workgroups, privates)); } liveGlobals.push_back(v.id); @@ -1572,9 +1592,10 @@ ShaderDebugTrace *Debugger::BeginDebug(DebugAPIWrapper *api, const ShaderStage s rdcarray threadIds; for(uint32_t i = 0; i < threadsInWorkgroup; i++) { + bool isActiveLane = (i == activeLaneIndex); ThreadState &lane = workgroup[i]; lane.workgroupIndex = i; - if(i != activeLaneIndex) + if(!isActiveLane) { lane.nextInstruction = active.nextInstruction; lane.outputs = active.outputs; @@ -1597,7 +1618,21 @@ ShaderDebugTrace *Debugger::BeginDebug(DebugAPIWrapper *api, const ShaderStage s // now that the globals are allocated and their storage won't move, we can take pointers to them for(const PointerId &p : pointerIDs) - p.Set(*this, global, lane); + p.Set(*this, global, lane, isActiveLane); + + if(isActiveLane) + { + for(const PointerId &p : pointerIDs) + { + // GSM pointers have a global and local index + // Create a GSM global pointer, used for writing back + if((p.globalIndex != UINT_MAX) && (p.threadIndex != UINT_MAX)) + { + RDCASSERTEQUAL(lane.gsmPointers.count(p.id), 0); + lane.gsmPointers[p.id] = MakePointerVariable(p.id, &global.workgroups[p.globalIndex]); + } + } + } // Only add active lanes to control flow if(!lane.dead)