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
This commit is contained in:
Jake Turner
2025-05-16 17:54:44 +01:00
parent 0fb918909e
commit 3f8df6b589
3 changed files with 137 additions and 17 deletions
+83 -7
View File
@@ -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<ThreadState>
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<ThreadState>
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<ThreadState>
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<ThreadState>
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
@@ -225,6 +225,14 @@ struct ThreadState
// changes (and vice-versa - a change via any of those pointers must update all other pointers).
SparseIdMap<rdcarray<Id>> pointersForId;
SparseIdMap<ShaderVariable> gsmPointers;
struct GSMIndex
{
int32_t global;
int32_t local;
};
rdcarray<GSMIndex> 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<ThreadState> &workgroup);
ShaderDebugState *m_State = NULL;
@@ -1006,30 +1006,48 @@ ShaderDebugTrace *Debugger::BeginDebug(DebugAPIWrapper *api, const ShaderStage s
struct PointerId
{
PointerId(Id i, rdcarray<ShaderVariable> GlobalState::*th, rdcarray<ShaderVariable> &storage)
: id(i), globalStorage(th), index(storage.size() - 1)
: id(i), globalStorage(th), globalIndex(storage.size() - 1)
{
}
PointerId(Id i, rdcarray<ShaderVariable> ThreadState::*th, rdcarray<ShaderVariable> &storage)
: id(i), threadStorage(th), index(storage.size() - 1)
: id(i), threadStorage(th), threadIndex(storage.size() - 1)
{
}
PointerId(Id i, rdcarray<ShaderVariable> GlobalState::*global,
rdcarray<ShaderVariable> &globalVars, rdcarray<ShaderVariable> ThreadState::*thread,
rdcarray<ShaderVariable> &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<ShaderVariable> GlobalState::*globalStorage = NULL;
rdcarray<ShaderVariable> 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<PointerId> 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<ThreadIndex> 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)