Files
renderdoc/renderdoc/driver/shaders/spirv/spirv_debug.cpp
T
baldurk 6ebd752a9d Hold onto ShaderDebugState pointer inside ThreadState during SetDst
* This avoids needing to pass it down to all possible child functions that might
  need it.
2020-04-16 18:19:08 +01:00

2143 lines
58 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2020 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "spirv_debug.h"
#include <math.h>
#include <time.h>
#include "common/formatting.h"
#include "spirv_op_helpers.h"
static bool ContainsNaNInf(const ShaderVariable &val)
{
bool ret = false;
for(const ShaderVariable &member : val.members)
ret |= ContainsNaNInf(member);
int count = int(val.rows) * int(val.columns);
if(val.type == VarType::Float || val.type == VarType::Half)
{
for(int i = 0; i < count; i++)
{
ret |= isinf(val.value.fv[i]);
ret |= isnan(val.value.fv[i]) != 0;
}
}
else if(val.type == VarType::Double)
{
for(int i = 0; i < count; i++)
{
ret |= isinf(val.value.dv[i]);
ret |= isnan(val.value.dv[i]) != 0;
}
}
return ret;
}
namespace rdcspv
{
ThreadState::ThreadState(uint32_t workgroupIdx, Debugger &debug, const GlobalState &globalState)
: debugger(debug), global(globalState)
{
workgroupIndex = workgroupIdx;
nextInstruction = 0;
helperInvocation = false;
killed = false;
}
ThreadState::~ThreadState()
{
for(StackFrame *stack : callstack)
delete stack;
callstack.clear();
}
bool ThreadState::Finished() const
{
return helperInvocation || killed || callstack.empty();
}
void ThreadState::FillCallstack(ShaderDebugState &state)
{
for(const StackFrame *frame : callstack)
state.callstack.push_back(debugger.GetHumanName(frame->function));
}
void ThreadState::EnterFunction(const rdcarray<Id> &arguments)
{
Iter it = debugger.GetIterForInstruction(nextInstruction);
RDCASSERT(OpDecoder(it).op == Op::Function);
OpFunction func(it);
StackFrame *frame = new StackFrame();
frame->function = func.result;
// if there's a previous stack frame, save its live list
if(!callstack.empty())
{
callstack.back()->live = live;
callstack.back()->sourceVars = sourceVars;
}
// start with just globals
live = debugger.GetLiveGlobals();
sourceVars = debugger.GetGlobalSourceVars();
// process the outgoing scope
ProcessScopeChange(frame->live, live);
callstack.push_back(frame);
it++;
size_t arg = 0;
while(OpDecoder(it).op == Op::FunctionParameter)
{
OpFunctionParameter param(it);
if(arg <= arguments.size())
{
// function parameters are copied into function calls. Thus a function parameter that is a
// pointer does not have allocated storage for itself, it gets the pointer from the call site
// copied in and points to whatever storage that is.
// That means we don't have to allocate anything here, we just set up the ID and copy the
// value from the argument
SetDst(param.result, ids[arguments[arg]]);
}
else
{
RDCERR("Not enough function parameters!");
}
arg++;
it++;
}
// next should be the start of the first function block
RDCASSERT(OpDecoder(it).op == Op::Label);
lastBlock = curBlock = OpLabel(it).result;
it++;
size_t numVars = 0;
Iter varCounter = it;
while(OpDecoder(varCounter).op == Op::Variable)
{
varCounter++;
numVars++;
}
frame->locals.resize(numVars);
size_t i = 0;
// handle any variable declarations
while(OpDecoder(it).op == Op::Variable)
{
OpVariable decl(it);
ShaderVariable &stackvar = frame->locals[i];
stackvar.name = debugger.GetRawName(decl.result);
rdcstr sourceName = debugger.GetHumanName(decl.result);
// don't add source vars - SetDst below will do that
debugger.AllocateVariable(decl.result, decl.resultType, DebugVariableType::Undefined,
sourceName, stackvar);
if(decl.HasInitializer())
AssignValue(stackvar, ids[decl.initializer]);
SetDst(decl.result, debugger.MakePointerVariable(decl.result, &stackvar));
it++;
i++;
}
// next instruction is the first actual instruction we'll execute
nextInstruction = debugger.GetInstructionForIter(it);
}
const ShaderVariable &ThreadState::GetSrc(Id id) const
{
return ids[id];
}
void ThreadState::WritePointerValue(Id pointer, const ShaderVariable &val)
{
RDCASSERT(ids[pointer].type == VarType::GPUPointer);
// this is the only place we don't use SetDst because it's the only place that "violates" SSA
// i.e. changes an existing value. That way SetDst can always unconditionally assign values,
// and only here do we write through pointers
if(!m_State)
{
debugger.WriteThroughPointer(ids[pointer], val);
}
else
{
ShaderVariable &var = ids[pointer];
if(ContainsNaNInf(val))
m_State->flags |= ShaderEvents::GeneratedNanOrInf;
// if var is a pointer we update the underlying storage and generate at least one change,
// plus any additional ones for other pointers.
Id ptrid = debugger.GetPointerBaseId(var);
rdcarray<ShaderVariableChange> changes;
ShaderVariableChange basechange;
basechange.before = debugger.EvaluatePointerVariable(ids[ptrid]);
rdcarray<Id> &pointers = pointersForId[ptrid];
changes.resize(pointers.size());
// for every other pointer, evaluate its value now before
for(size_t i = 0; i < pointers.size(); i++)
changes[i].before = debugger.EvaluatePointerVariable(ids[pointers[i]]);
debugger.WriteThroughPointer(var, val);
// now evaluate the value after
for(size_t i = 0; i < pointers.size(); i++)
changes[i].after = debugger.EvaluatePointerVariable(ids[pointers[i]]);
// if the pointer we're writing is one of the aliased pointers, be sure we add it even if
// it's a no-op change
int ptrIdx = pointers.indexOf(pointer);
if(ptrIdx >= 0)
{
m_State->changes.push_back(changes[ptrIdx]);
changes.erase(ptrIdx);
}
// remove any no-op changes. Some pointers might point to the same ID but a child that
// wasn't written to. Note that this might not actually mean nothing was changed (if e.g.
// we're assigning the same value) but that false negative is not a concern.
changes.removeIf([](const ShaderVariableChange &c) { return c.before == c.after; });
m_State->changes.append(changes);
// always add a change for the base storage variable written itself, even if that's a no-op.
// This one is not included in any of the pointers lists above
basechange.after = debugger.EvaluatePointerVariable(ids[ptrid]);
m_State->changes.push_back(basechange);
}
}
void ThreadState::SetDst(Id id, const ShaderVariable &val)
{
if(m_State && ContainsNaNInf(val))
m_State->flags |= ShaderEvents::GeneratedNanOrInf;
ids[id] = val;
ids[id].name = debugger.GetRawName(id);
auto it = std::lower_bound(live.begin(), live.end(), id);
live.insert(it - live.begin(), id);
if(m_State)
{
ShaderVariableChange change;
change.after = debugger.EvaluatePointerVariable(ids[id]);
m_State->changes.push_back(change);
debugger.AddSourceVars(sourceVars, id);
}
}
void ThreadState::ProcessScopeChange(const rdcarray<Id> &oldLive, const rdcarray<Id> &newLive)
{
// nothing to do if we aren't tracking into a state
if(!m_State)
return;
// all oldLive (except globals) are going out of scope. all newLive (except globals) are coming
// into scope
const rdcarray<Id> &liveGlobals = debugger.GetLiveGlobals();
for(const Id id : oldLive)
{
if(liveGlobals.contains(id))
continue;
m_State->changes.push_back({debugger.EvaluatePointerVariable(ids[id])});
}
for(const Id id : newLive)
{
if(liveGlobals.contains(id))
continue;
m_State->changes.push_back({ShaderVariable(), debugger.EvaluatePointerVariable(ids[id])});
}
}
ShaderVariable ThreadState::CalcDeriv(ThreadState::DerivDir dir, ThreadState::DerivType type,
const rdcarray<ThreadState> &workgroup, Id val)
{
const bool xdirection = (dir == DDX);
if(type == Coarse)
{
// coarse derivatives are identical across the quad, based on the top-left.
ShaderVariable a = workgroup[0].GetSrc(val);
ShaderVariable b = workgroup[xdirection ? 1 : 2].GetSrc(val);
for(uint8_t c = 0; c < a.columns; c++)
a.value.fv[c] = b.value.fv[c] - a.value.fv[c];
return a;
}
else
{
ShaderVariable a, b;
// we need to figure out the exact pair to use
int x = workgroupIndex & 1;
int y = workgroupIndex / 2;
if(x == 0)
{
if(y == 0)
{
// top-left
if(xdirection)
{
a = workgroup[0].GetSrc(val);
b = workgroup[1].GetSrc(val);
}
else
{
a = workgroup[0].GetSrc(val);
b = workgroup[2].GetSrc(val);
}
}
else
{
// bottom-left
if(xdirection)
{
a = workgroup[2].GetSrc(val);
b = workgroup[3].GetSrc(val);
}
else
{
a = workgroup[0].GetSrc(val);
b = workgroup[2].GetSrc(val);
}
}
}
else
{
if(y == 0)
{
// top-right
if(xdirection)
{
a = workgroup[0].GetSrc(val);
b = workgroup[1].GetSrc(val);
}
else
{
a = workgroup[1].GetSrc(val);
b = workgroup[3].GetSrc(val);
}
}
else
{
// bottom-right
if(xdirection)
{
a = workgroup[2].GetSrc(val);
b = workgroup[3].GetSrc(val);
}
else
{
a = workgroup[1].GetSrc(val);
b = workgroup[3].GetSrc(val);
}
}
}
// do the subtract
for(uint8_t c = 0; c < a.columns; c++)
a.value.fv[c] = b.value.fv[c] - a.value.fv[c];
return a;
}
}
void ThreadState::JumpToLabel(Id target)
{
lastBlock = curBlock;
curBlock = target;
nextInstruction = debugger.GetInstructionForLabel(target) + 1;
// if jumping to an empty unconditional loop header, continue to the loop block
Iter it = debugger.GetIterForInstruction(nextInstruction);
if(it.opcode() == Op::LoopMerge)
{
it++;
if(it.opcode() == Op::Branch)
{
JumpToLabel(OpBranch(it).targetLabel);
}
}
}
void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState> &workgroup)
{
m_State = state;
Iter it = debugger.GetIterForInstruction(nextInstruction);
nextInstruction++;
OpDecoder opdata(it);
// skip OpLine/OpNoLine
while(opdata.op == Op::Line || opdata.op == Op::NoLine)
{
it++;
nextInstruction++;
opdata = OpDecoder(it);
}
// for now we don't care about structured control flow so skip past merge statements so we process
// the branch. OpLine can't be in between so we can safely advance
if(opdata.op == Op::SelectionMerge || opdata.op == Op::LoopMerge)
{
it++;
nextInstruction++;
opdata = OpDecoder(it);
}
switch(opdata.op)
{
//////////////////////////////////////////////////////////////////////////////
//
// Pointer manipulation opcodes
//
//////////////////////////////////////////////////////////////////////////////
case Op::Load:
{
// we currently handle pointers as fixed storage, so a load becomes a copy
OpLoad load(it);
// ignore
(void)load.memoryAccess;
// get the pointer value, evaluate it (i.e. dereference) and store the result
SetDst(load.result, debugger.EvaluatePointerVariable(GetSrc(load.pointer)));
break;
}
case Op::Store:
{
OpStore store(it);
// ignore
(void)store.memoryAccess;
WritePointerValue(store.pointer, GetSrc(store.object));
break;
}
case Op::AccessChain:
case Op::InBoundsAccessChain:
{
OpAccessChain chain(it);
rdcarray<uint32_t> indices;
// evaluate the indices
indices.reserve(chain.indexes.size());
for(Id id : chain.indexes)
indices.push_back(GetSrc(id).value.u.x);
SetDst(chain.result, debugger.MakeCompositePointer(ids[chain.base], chain.base, indices));
break;
}
//////////////////////////////////////////////////////////////////////////////
//
// Derivative opcodes
//
//////////////////////////////////////////////////////////////////////////////
// spec allows the implementation to choose what DPdx means (coarse or fine), so we choose
// coarse which seems a reasonable default. In future we could driver-detect the selection in
// use (assuming it's not dynamic base on circumstances)
case Op::DPdx:
case Op::DPdy:
case Op::DPdxCoarse:
case Op::DPdyCoarse:
case Op::DPdxFine:
case Op::DPdyFine:
{
// these all share a format
OpDPdx deriv(it);
DerivDir dir = DDX;
if(opdata.op == Op::DPdy || opdata.op == Op::DPdyCoarse || opdata.op == Op::DPdyFine)
dir = DDY;
DerivType type = Coarse;
if(opdata.op == Op::DPdxFine || opdata.op == Op::DPdyFine)
type = Fine;
SetDst(deriv.result, CalcDeriv(dir, type, workgroup, deriv.p));
break;
}
case Op::Fwidth:
case Op::FwidthCoarse:
case Op::FwidthFine:
{
// these all share a format
OpFwidth deriv(it);
DerivType type = Coarse;
if(opdata.op == Op::FwidthFine)
type = Fine;
ShaderVariable var = CalcDeriv(DDX, type, workgroup, deriv.p);
ShaderVariable ddy = CalcDeriv(DDY, type, workgroup, deriv.p);
for(uint32_t c = 0; c < var.columns; c++)
var.value.fv[c] = fabsf(var.value.fv[c]) + fabsf(ddy.value.fv[c]);
SetDst(deriv.result, var);
break;
}
//////////////////////////////////////////////////////////////////////////////
//
// Composite/vector opcodes
//
//////////////////////////////////////////////////////////////////////////////
case Op::CompositeExtract:
{
OpCompositeExtract extract(it);
// to re-use composite/access chain logic, temporarily make a pointer to the composite
// (illegal in SPIR-V)
ShaderVariable ptr =
debugger.MakeCompositePointer(ids[extract.composite], extract.composite, extract.indexes);
// then evaluate it, to get the extracted value
SetDst(extract.result, debugger.EvaluatePointerVariable(ptr));
break;
}
case Op::CompositeInsert:
{
OpCompositeInsert insert(it);
ShaderVariable var = GetSrc(insert.composite);
ShaderVariable obj = GetSrc(insert.object);
// walk any struct member indices
ShaderVariable *mod = &var;
size_t i = 0;
while(i < insert.indexes.size() && !mod->members.empty())
{
mod = &mod->members[insert.indexes[i]];
i++;
}
if(i == insert.indexes.size())
{
// if there are no more indices, replace the object here
mod->value = obj.value;
}
else if(i + 1 == insert.indexes.size())
{
// one more index
uint32_t idx = insert.indexes[i];
// if it's a matrix, replace a whole (column) vector
if(mod->rows > 1)
{
uint32_t column = idx;
RDCASSERTEQUAL(mod->rows, obj.columns);
for(uint32_t row = 0; row < mod->rows; row++)
{
if(VarTypeByteSize(mod->type) == 8)
mod->value.u64v[row * mod->columns + column] = obj.value.u64v[row];
else
mod->value.uv[row * mod->columns + column] = obj.value.uv[row];
}
}
else
{
// if it's a vector, replace one scalar
if(VarTypeByteSize(mod->type) == 8)
mod->value.u64v[idx] = obj.value.u64v[0];
else
mod->value.uv[idx] = obj.value.uv[0];
}
}
else if(i + 2 == insert.indexes.size())
{
// two more indices, selecting column then scalar in a matrix
uint32_t column = insert.indexes[i];
uint32_t row = insert.indexes[i + 1];
if(VarTypeByteSize(mod->type) == 8)
mod->value.u64v[row * mod->columns + column] = obj.value.u64v[0];
else
mod->value.uv[row * mod->columns + column] = obj.value.uv[0];
}
// then evaluate it, to get the extracted value
SetDst(insert.result, var);
break;
}
case Op::CompositeConstruct:
{
OpCompositeConstruct construct(it);
ShaderVariable var;
const DataType &type = debugger.GetType(construct.resultType);
RDCASSERT(!construct.constituents.empty());
if(type.type == DataType::ArrayType || type.type == DataType::StructType)
{
var.members.resize(construct.constituents.size());
for(size_t i = 0; i < construct.constituents.size(); i++)
{
ShaderVariable &mem = var.members[i];
mem = GetSrc(construct.constituents[i]);
if(type.type == DataType::ArrayType)
mem.name = StringFormat::Fmt("[%zu]", i);
else
mem.name = StringFormat::Fmt("_child%zu", i);
}
}
else if(type.type == DataType::VectorType)
{
RDCASSERT(construct.constituents.size() <= 4);
var.type = type.scalar().Type();
var.rows = 1;
var.columns = RDCMAX(1U, type.vector().count);
// it is possible to construct larger vectors from a collection of scalars and smaller
// vectors.
size_t dst = 0;
for(size_t i = 0; i < construct.constituents.size(); i++)
{
ShaderVariable src = GetSrc(construct.constituents[i]);
RDCASSERTEQUAL(src.rows, 1);
for(size_t j = 0; j < src.columns; j++)
{
if(VarTypeByteSize(var.type) == 8)
var.value.u64v[dst++] = src.value.u64v[j];
else
var.value.uv[dst++] = src.value.uv[j];
}
}
}
else if(type.type == DataType::MatrixType)
{
// matrices are constructed from a list of columns
var.type = type.scalar().Type();
var.columns = RDCMAX(1U, type.matrix().count);
var.rows = RDCMAX(1U, type.vector().count);
RDCASSERTEQUAL(var.columns, construct.constituents.size());
rdcarray<ShaderVariable> columns;
columns.resize(construct.constituents.size());
for(size_t i = 0; i < construct.constituents.size(); i++)
columns[i] = GetSrc(construct.constituents[i]);
for(size_t r = 0; r < var.rows; r++)
{
for(size_t c = 0; c < var.columns; c++)
{
if(VarTypeByteSize(var.type) == 8)
var.value.u64v[r * var.columns + c] = columns[c].value.u64v[r];
else
var.value.uv[r * var.columns + c] = columns[c].value.uv[r];
}
}
}
SetDst(construct.result, var);
break;
}
case Op::VectorShuffle:
{
OpVectorShuffle shuffle(it);
ShaderVariable var;
const DataType &type = debugger.GetType(shuffle.resultType);
var.type = type.scalar().Type();
var.rows = 1;
var.columns = RDCMAX(1U, (uint32_t)shuffle.components.size());
ShaderVariable src1 = GetSrc(shuffle.vector1);
ShaderVariable src2 = GetSrc(shuffle.vector2);
for(size_t i = 0; i < shuffle.components.size(); i++)
{
uint32_t c = shuffle.components[i];
if(c <= 3)
var.value.uv[i] = src1.value.uv[c];
else
var.value.uv[i] = src2.value.uv[c - 4];
}
SetDst(shuffle.result, var);
break;
}
case Op::VectorExtractDynamic:
{
OpVectorExtractDynamic extract(it);
ShaderVariable var = GetSrc(extract.vector);
ShaderVariable idx = GetSrc(extract.index);
uint32_t comp = idx.value.u.x;
if(VarTypeByteSize(var.type) == 8)
var.value.u64v[0] = var.value.u64v[comp];
else
var.value.uv[0] = var.value.uv[comp];
// result is now scalar
var.columns = 1;
SetDst(extract.result, var);
break;
}
case Op::VectorInsertDynamic:
{
OpVectorInsertDynamic insert(it);
ShaderVariable var = GetSrc(insert.vector);
ShaderVariable scalar = GetSrc(insert.component);
ShaderVariable idx = GetSrc(insert.index);
uint32_t comp = idx.value.u.x;
if(VarTypeByteSize(var.type) == 8)
var.value.u64v[comp] = scalar.value.u64v[0];
else
var.value.uv[comp] = scalar.value.uv[0];
SetDst(insert.result, var);
break;
}
case Op::Select:
{
OpSelect select(it);
// we treat this as a composite instruction for the case where the condition is a vector
ShaderVariable cond = GetSrc(select.condition);
ShaderVariable var = GetSrc(select.object1);
ShaderVariable b = GetSrc(select.object2);
if(cond.columns == 1)
{
if(cond.value.u.x == 0)
var = b;
}
else
{
for(uint8_t c = 0; c < cond.columns; c++)
{
if(cond.value.uv[c] == 0)
{
if(VarTypeByteSize(var.type) == 8)
var.value.u64v[c] = b.value.u64v[c];
else
var.value.uv[c] = b.value.uv[c];
}
}
}
SetDst(select.result, var);
break;
}
//////////////////////////////////////////////////////////////////////////////
//
// Conversion opcodes
//
//////////////////////////////////////////////////////////////////////////////
case Op::ConvertFToS:
case Op::ConvertFToU:
case Op::ConvertSToF:
case Op::ConvertUToF:
{
OpConvertFToS conv(it);
ShaderVariable var = GetSrc(conv.floatValue);
if(opdata.op == Op::ConvertFToS)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.iv[c] = (int)var.value.fv[c];
var.type = VarType::SInt;
}
else if(opdata.op == Op::ConvertFToU)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = var.value.fv[c] > 0.0f ? (uint32_t)var.value.fv[c] : 0U;
var.type = VarType::UInt;
}
else if(opdata.op == Op::ConvertSToF)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.fv[c] = (float)var.value.iv[c];
var.type = VarType::Float;
}
else if(opdata.op == Op::ConvertUToF)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.fv[c] = (float)var.value.uv[c];
var.type = VarType::Float;
}
SetDst(conv.result, var);
break;
}
case Op::Bitcast:
{
OpBitcast cast(it);
const DataType &type = debugger.GetType(cast.resultType);
ShaderVariable var = GetSrc(cast.operand);
if((type.type == DataType::ScalarType && var.columns == 1) || type.vector().count == var.columns)
{
// if the column count is unchanged, just change the underlying type
var.type = type.scalar().Type();
}
else
{
uint32_t srcByteCount = 4;
if(var.type == VarType::Double || var.type == VarType::ULong || var.type == VarType::SLong)
srcByteCount = 8;
else if(var.type == VarType::Half || var.type == VarType::UShort ||
var.type == VarType::SShort)
srcByteCount = 2;
else if(var.type == VarType::UByte || var.type == VarType::SByte)
srcByteCount = 1;
uint32_t dstByteCount = type.scalar().width / 8;
// must be identical bit count
RDCASSERT(dstByteCount * type.vector().count == srcByteCount * var.columns);
uint32_t byteSize = VarTypeByteSize(var.type);
bytebuf bytes;
for(uint32_t c = 0; c < var.columns; c++)
{
if(byteSize == 8)
bytes.append((const byte *)&var.value.u64v[c], byteSize);
else
bytes.append((const byte *)&var.value.uv[c], byteSize);
}
var.type = type.scalar().Type();
var.columns = type.vector().count;
var.value = ShaderValue();
byte *b = bytes.data();
for(uint32_t c = 0; c < var.columns; c++)
{
if(byteSize == 8)
memcpy(&var.value.u64v[c], b, byteSize);
else
memcpy(&var.value.uv[c], b, byteSize);
b += byteSize;
}
}
SetDst(cast.result, var);
break;
}
//////////////////////////////////////////////////////////////////////////////
//
// Extended instruction set handling
//
//////////////////////////////////////////////////////////////////////////////
case Op::ExtInst:
{
Id result = Id::fromWord(it.word(2));
Id extinst = Id::fromWord(it.word(3));
if(global.extInsts.find(extinst) == global.extInsts.end())
{
RDCERR("Unknown extended instruction set %u", extinst.value());
break;
}
const ExtInstDispatcher &dispatch = global.extInsts[extinst];
// ignore nonsemantic instructions
if(dispatch.nonsemantic)
break;
uint32_t instruction = it.word(4);
if(instruction >= dispatch.functions.size())
{
RDCERR("Unsupported instruction %u in set %s (only %zu instructions defined)", instruction,
dispatch.name.c_str(), dispatch.functions.size());
break;
}
if(dispatch.functions[instruction] == NULL)
{
RDCWARN("Unimplemented extended instruction %s::%s", dispatch.name.c_str(),
dispatch.names[instruction].c_str());
break;
}
rdcarray<Id> params;
for(size_t i = 5; i < it.size(); i++)
params.push_back(Id::fromWord(it.word(i)));
SetDst(result, dispatch.functions[instruction](*this, instruction, params));
break;
}
//////////////////////////////////////////////////////////////////////////////
//
// Comparison opcodes
//
//////////////////////////////////////////////////////////////////////////////
case Op::LogicalEqual:
case Op::LogicalNotEqual:
case Op::LogicalOr:
case Op::LogicalAnd:
case Op::IEqual:
case Op::INotEqual:
case Op::UGreaterThan:
case Op::UGreaterThanEqual:
case Op::ULessThan:
case Op::ULessThanEqual:
case Op::SGreaterThan:
case Op::SGreaterThanEqual:
case Op::SLessThan:
case Op::SLessThanEqual:
case Op::FOrdEqual:
case Op::FOrdNotEqual:
case Op::FOrdGreaterThan:
case Op::FOrdGreaterThanEqual:
case Op::FOrdLessThan:
case Op::FOrdLessThanEqual:
case Op::FUnordEqual:
case Op::FUnordNotEqual:
case Op::FUnordGreaterThan:
case Op::FUnordGreaterThanEqual:
case Op::FUnordLessThan:
case Op::FUnordLessThanEqual:
{
OpFMul comp(it);
ShaderVariable var = GetSrc(comp.operand1);
ShaderVariable b = GetSrc(comp.operand2);
if(opdata.op == Op::IEqual || opdata.op == Op::LogicalEqual)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.uv[c] == b.value.uv[c]) ? 1 : 0;
}
else if(opdata.op == Op::INotEqual || opdata.op == Op::LogicalNotEqual)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.uv[c] != b.value.uv[c]) ? 1 : 0;
}
else if(opdata.op == Op::LogicalAnd)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = var.value.uv[c] & b.value.uv[c];
}
else if(opdata.op == Op::LogicalOr)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = var.value.uv[c] | b.value.uv[c];
}
else if(opdata.op == Op::UGreaterThan)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.uv[c] > b.value.uv[c]) ? 1 : 0;
}
else if(opdata.op == Op::UGreaterThanEqual)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.uv[c] >= b.value.uv[c]) ? 1 : 0;
}
else if(opdata.op == Op::ULessThan)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.uv[c] < b.value.uv[c]) ? 1 : 0;
}
else if(opdata.op == Op::ULessThanEqual)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.uv[c] <= b.value.uv[c]) ? 1 : 0;
}
else if(opdata.op == Op::SGreaterThan)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.iv[c] > b.value.iv[c]) ? 1 : 0;
}
else if(opdata.op == Op::SGreaterThanEqual)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.iv[c] >= b.value.iv[c]) ? 1 : 0;
}
else if(opdata.op == Op::SLessThan)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.iv[c] < b.value.iv[c]) ? 1 : 0;
}
else if(opdata.op == Op::SLessThanEqual)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.iv[c] <= b.value.iv[c]) ? 1 : 0;
}
// FOrd are all "Floating-point comparison if operands are ordered and Operand 1 is ... than
// Operand 2.".
// Since NaN is the only unordered value, and NaN comparisons are always false, we can take
// advantage of that by FOrd just being straight comparisons. If the operands are unordered
// (i.e. one is NaN) then the FOrd variatns return false as expected.
//
// FUnord are all "Floating-point comparison if operands are unordered or Operand 1 is ...
// than Operand 2."
// Again as above, any comparison with unordered comparisons will return false. Since we want
// 'or are unordered' then we want to negate the comparison so that unordered comparisons will
// always return true. So we negate and invert the actual comparison so that the comparison
// will be unchanged effectively.
if(opdata.op == Op::FOrdEqual)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.fv[c] == b.value.fv[c]) ? 1 : 0;
}
else if(opdata.op == Op::FOrdNotEqual)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.fv[c] != b.value.fv[c]) ? 1 : 0;
}
else if(opdata.op == Op::FOrdGreaterThan)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.fv[c] > b.value.fv[c]) ? 1 : 0;
}
else if(opdata.op == Op::FOrdGreaterThanEqual)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.fv[c] >= b.value.fv[c]) ? 1 : 0;
}
else if(opdata.op == Op::FOrdLessThan)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.fv[c] < b.value.fv[c]) ? 1 : 0;
}
else if(opdata.op == Op::FOrdLessThanEqual)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.fv[c] <= b.value.fv[c]) ? 1 : 0;
}
if(opdata.op == Op::FUnordEqual)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.fv[c] != b.value.fv[c]) ? 0 : 1;
}
else if(opdata.op == Op::FUnordNotEqual)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.fv[c] == b.value.fv[c]) ? 0 : 1;
}
else if(opdata.op == Op::FUnordGreaterThan)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.fv[c] <= b.value.fv[c]) ? 0 : 1;
}
else if(opdata.op == Op::FUnordGreaterThanEqual)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.fv[c] < b.value.fv[c]) ? 0 : 1;
}
else if(opdata.op == Op::FUnordLessThan)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.fv[c] >= b.value.fv[c]) ? 0 : 1;
}
else if(opdata.op == Op::FUnordLessThanEqual)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = (var.value.fv[c] > b.value.fv[c]) ? 0 : 1;
}
// TODO we should add a bool type
var.type = VarType::UInt;
SetDst(comp.result, var);
break;
}
case Op::LogicalNot:
{
OpLogicalNot negate(it);
ShaderVariable var = GetSrc(negate.operand);
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = 1U - var.value.uv[c];
SetDst(negate.result, var);
break;
}
case Op::Any:
case Op::All:
{
OpAny any(it);
ShaderVariable var = GetSrc(any.vector);
for(uint8_t c = 1; c < var.columns; c++)
{
if(opdata.op == Op::Any)
var.value.uv[0] |= var.value.uv[c];
else
var.value.uv[0] &= var.value.uv[c];
}
var.columns = 1;
SetDst(any.result, var);
break;
}
case Op::IsNan:
{
OpIsNan is(it);
ShaderVariable var = GetSrc(is.x);
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = isnan(var.value.fv[c]) ? 1 : 0;
// TODO we should add a bool type
var.type = VarType::UInt;
SetDst(is.result, var);
break;
}
case Op::IsInf:
{
OpIsNan is(it);
ShaderVariable var = GetSrc(is.x);
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = isinf(var.value.fv[c]) ? 1 : 0;
// TODO we should add a bool type
var.type = VarType::UInt;
SetDst(is.result, var);
break;
}
//////////////////////////////////////////////////////////////////////////////
//
// Bitwise/logical opcodes
//
//////////////////////////////////////////////////////////////////////////////
case Op::BitwiseOr:
case Op::BitwiseAnd:
case Op::BitwiseXor:
case Op::ShiftLeftLogical:
case Op::ShiftRightArithmetic:
case Op::ShiftRightLogical:
{
OpBitwiseOr bitwise(it);
ShaderVariable var = GetSrc(bitwise.operand1);
ShaderVariable b = GetSrc(bitwise.operand2);
if(opdata.op == Op::BitwiseOr)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = var.value.uv[c] | b.value.uv[c];
}
else if(opdata.op == Op::BitwiseAnd)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = var.value.uv[c] & b.value.uv[c];
}
else if(opdata.op == Op::BitwiseXor)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = var.value.uv[c] ^ b.value.uv[c];
}
else if(opdata.op == Op::ShiftLeftLogical)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = var.value.uv[c] << b.value.uv[c];
}
else if(opdata.op == Op::ShiftRightArithmetic)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.iv[c] = var.value.iv[c] >> b.value.uv[c];
}
else if(opdata.op == Op::ShiftRightLogical)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = var.value.uv[c] >> b.value.uv[c];
}
SetDst(bitwise.result, var);
break;
}
case Op::Not:
{
OpNot bitwise(it);
ShaderVariable var = GetSrc(bitwise.operand);
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = ~var.value.uv[c];
SetDst(bitwise.result, var);
break;
}
//////////////////////////////////////////////////////////////////////////////
//
// Mathematical opcodes
//
//////////////////////////////////////////////////////////////////////////////
case Op::FMul:
case Op::FDiv:
case Op::FMod:
case Op::FRem:
case Op::FAdd:
case Op::FSub:
case Op::IMul:
case Op::SDiv:
case Op::UDiv:
case Op::UMod:
case Op::SMod:
case Op::SRem:
case Op::IAdd:
case Op::ISub:
{
OpFMul math(it);
ShaderVariable var = GetSrc(math.operand1);
ShaderVariable b = GetSrc(math.operand2);
if(opdata.op == Op::FMul)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.fv[c] *= b.value.fv[c];
}
else if(opdata.op == Op::FDiv)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.fv[c] /= b.value.fv[c];
}
else if(opdata.op == Op::FMod)
{
for(uint8_t c = 0; c < var.columns; c++)
{
float af = var.value.fv[c], bf = b.value.fv[c];
var.value.fv[c] = fmodf(af, bf);
if(var.value.fv[c] < 0.0f && bf >= 0.0f)
var.value.fv[c] += fabsf(bf);
else if(var.value.fv[c] >= 0.0f && bf < 0.0f)
var.value.fv[c] -= fabsf(bf);
}
}
else if(opdata.op == Op::FRem)
{
for(uint8_t c = 0; c < var.columns; c++)
{
float af = var.value.fv[c], bf = b.value.fv[c];
var.value.fv[c] = fmodf(af, bf);
if(var.value.fv[c] < 0.0f && af >= 0.0f)
var.value.fv[c] += fabsf(bf);
else if(var.value.fv[c] >= 0.0f && af < 0.0f)
var.value.fv[c] -= fabsf(bf);
}
}
else if(opdata.op == Op::FAdd)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.fv[c] += b.value.fv[c];
}
else if(opdata.op == Op::FSub)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.fv[c] -= b.value.fv[c];
}
else if(opdata.op == Op::IMul)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] *= b.value.uv[c];
}
else if(opdata.op == Op::SDiv)
{
for(uint8_t c = 0; c < var.columns; c++)
{
if(b.value.iv[c] != 0)
{
var.value.iv[c] /= b.value.iv[c];
}
else
{
var.value.uv[c] = ~0U;
if(m_State)
m_State->flags |= ShaderEvents::GeneratedNanOrInf;
}
}
}
else if(opdata.op == Op::UDiv)
{
for(uint8_t c = 0; c < var.columns; c++)
{
if(b.value.uv[c] != 0)
{
var.value.uv[c] /= b.value.uv[c];
}
else
{
var.value.uv[c] = ~0U;
if(m_State)
m_State->flags |= ShaderEvents::GeneratedNanOrInf;
}
}
}
else if(opdata.op == Op::UMod)
{
for(uint8_t c = 0; c < var.columns; c++)
{
if(b.value.uv[c] != 0)
{
var.value.uv[c] %= b.value.uv[c];
}
else
{
var.value.uv[c] = ~0U;
if(m_State)
m_State->flags |= ShaderEvents::GeneratedNanOrInf;
}
}
}
else if(opdata.op == Op::SRem || opdata.op == Op::SMod)
{
for(uint8_t c = 0; c < var.columns; c++)
{
if(b.value.iv[c] != 0)
{
var.value.iv[c] %= b.value.iv[c];
}
else
{
var.value.uv[c] = ~0U;
if(m_State)
m_State->flags |= ShaderEvents::GeneratedNanOrInf;
}
}
}
else if(opdata.op == Op::IAdd)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] += b.value.uv[c];
}
else if(opdata.op == Op::ISub)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] -= b.value.uv[c];
}
SetDst(math.result, var);
break;
}
case Op::FNegate:
case Op::SNegate:
{
OpFNegate math(it);
ShaderVariable var = GetSrc(math.operand);
if(opdata.op == Op::FNegate)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.fv[c] = -var.value.fv[c];
}
else if(opdata.op == Op::SNegate)
{
for(uint8_t c = 0; c < var.columns; c++)
var.value.iv[c] = -var.value.iv[c];
}
SetDst(math.result, var);
break;
}
case Op::Dot:
{
OpDot dot(it);
ShaderVariable var = GetSrc(dot.vector1);
ShaderVariable b = GetSrc(dot.vector2);
RDCASSERTEQUAL(var.columns, b.columns);
float ret = 0;
for(uint8_t c = 0; c < var.columns; c++)
ret += var.value.fv[c] * b.value.fv[c];
var.columns = 1;
var.value.f.x = ret;
SetDst(dot.result, var);
break;
}
case Op::VectorTimesScalar:
{
OpVectorTimesScalar mul(it);
ShaderVariable var = GetSrc(mul.vector);
ShaderVariable scalar = GetSrc(mul.scalar);
for(uint8_t c = 0; c < var.columns; c++)
var.value.fv[c] *= scalar.value.f.x;
SetDst(mul.result, var);
break;
}
case Op::MatrixTimesScalar:
{
OpMatrixTimesScalar mul(it);
ShaderVariable var = GetSrc(mul.matrix);
ShaderVariable scalar = GetSrc(mul.scalar);
for(uint8_t c = 0; c < var.rows * var.columns; c++)
var.value.fv[c] *= scalar.value.f.x;
SetDst(mul.result, var);
break;
}
case Op::VectorTimesMatrix:
{
OpVectorTimesMatrix mul(it);
ShaderVariable matrix = GetSrc(mul.matrix);
ShaderVariable vector = GetSrc(mul.vector);
ShaderVariable var = vector;
var.columns = matrix.columns;
float *m = matrix.value.fv;
float *v = vector.value.fv;
const DataType &type = debugger.GetType(mul.resultType);
RDCASSERTEQUAL(type.vector().count, var.columns);
RDCASSERTEQUAL(matrix.rows, vector.columns);
for(uint8_t c = 0; c < matrix.columns; c++)
{
var.value.fv[c] = 0.0f;
for(uint8_t r = 0; r < matrix.rows; r++)
{
var.value.fv[c] += m[r * matrix.columns + c] * v[r];
}
}
SetDst(mul.result, var);
break;
}
case Op::Transpose:
{
OpTranspose transpose(it);
ShaderVariable matrix = GetSrc(transpose.matrix);
ShaderVariable var = matrix;
std::swap(var.rows, var.columns);
for(uint8_t r = 0; r < var.rows; r++)
for(uint8_t c = 0; c < var.columns; c++)
var.value.fv[r * var.columns + c] = matrix.value.fv[c * matrix.columns + r];
SetDst(transpose.result, var);
break;
}
case Op::MatrixTimesVector:
{
OpMatrixTimesVector mul(it);
ShaderVariable matrix = GetSrc(mul.matrix);
ShaderVariable vector = GetSrc(mul.vector);
ShaderVariable var = vector;
var.columns = matrix.rows;
float *m = matrix.value.fv;
float *v = vector.value.fv;
const DataType &type = debugger.GetType(mul.resultType);
RDCASSERTEQUAL(type.vector().count, var.columns);
RDCASSERTEQUAL(matrix.columns, vector.columns);
for(uint8_t r = 0; r < matrix.rows; r++)
{
var.value.fv[r] = 0.0f;
for(uint8_t c = 0; c < matrix.columns; c++)
{
var.value.fv[r] += m[r * matrix.columns + c] * v[c];
}
}
SetDst(mul.result, var);
break;
}
case Op::MatrixTimesMatrix:
{
OpMatrixTimesMatrix mul(it);
ShaderVariable left = GetSrc(mul.leftMatrix);
ShaderVariable right = GetSrc(mul.rightMatrix);
ShaderVariable var = left;
var.rows = left.rows;
var.columns = right.columns;
float *l = left.value.fv;
float *r = right.value.fv;
RDCASSERTEQUAL(left.columns, right.rows);
for(uint8_t dstr = 0; dstr < var.rows; dstr++)
{
for(uint8_t dstc = 0; dstc < var.columns; dstc++)
{
float &dstval = var.value.fv[dstr * var.columns + dstc];
dstval = 0.0f;
for(uint8_t src = 0; src < right.rows; src++)
{
dstval += l[dstr * left.columns + src] * r[src * right.columns + dstc];
}
}
}
SetDst(mul.result, var);
break;
}
case Op::OuterProduct:
{
OpOuterProduct mul(it);
ShaderVariable left = GetSrc(mul.vector1);
ShaderVariable right = GetSrc(mul.vector2);
ShaderVariable var = left;
var.rows = left.columns;
var.columns = right.columns;
for(uint8_t r = 0; r < var.rows; r++)
{
for(uint8_t c = 0; c < var.columns; c++)
{
var.value.fv[r * var.columns + c] = left.value.fv[r] * right.value.fv[c];
}
}
SetDst(mul.result, var);
break;
}
//////////////////////////////////////////////////////////////////////////////
//
// Image opcodes
//
//////////////////////////////////////////////////////////////////////////////
case Op::SampledImage:
{
OpSampledImage sampled(it);
// we make a little struct out of the combination
ShaderVariable result;
result.rows = 1;
result.columns = 1;
result.isStruct = true;
result.members = {GetSrc(sampled.image), GetSrc(sampled.sampler)};
result.members[0].name = "_child0";
result.members[1].name = "_child1";
SetDst(opdata.result, result);
break;
}
case Op::Image:
{
OpImage image(it);
ShaderVariable var = GetSrc(image.sampledImage);
// if this is a struct, pull out the image. Otherwise leave it alone because it's just a
// reference to a binding which we use as-is.
if(!var.members.empty())
var = var.members[0];
SetDst(image.result, var);
break;
}
case Op::ImageFetch:
case Op::ImageSampleExplicitLod:
case Op::ImageSampleImplicitLod:
{
ShaderVariable img;
ShaderVariable sampler;
ShaderVariable uv;
ShaderVariable ddxCalc;
ShaderVariable ddyCalc;
ShaderVariable compare;
ImageOperandsAndParamDatas operands;
GatherChannel gather = GatherChannel::Red;
Id derivId;
if(opdata.op == Op::ImageFetch)
{
OpImageFetch image(it);
img = GetSrc(image.image);
uv = GetSrc(image.coordinate);
operands = image.imageOperands;
}
else if(opdata.op == Op::ImageSampleExplicitLod)
{
OpImageSampleExplicitLod image(it);
sampler = img = GetSrc(image.sampledImage);
uv = GetSrc(image.coordinate);
operands = image.imageOperands;
}
else if(opdata.op == Op::ImageSampleImplicitLod)
{
OpImageSampleImplicitLod image(it);
sampler = img = GetSrc(image.sampledImage);
uv = GetSrc(image.coordinate);
operands = image.imageOperands;
derivId = image.coordinate;
}
if(derivId != Id())
{
// calculate DDX/DDY in coarse fashion
ddxCalc = CalcDeriv(DDX, Coarse, workgroup, derivId);
ddyCalc = CalcDeriv(DDY, Coarse, workgroup, derivId);
}
// if we have a dynamically combined image sampler, split it up here
if(!img.members.empty() && !sampler.members.empty())
{
img = img.members[0];
sampler = sampler.members[1];
}
const DataType &resultType = debugger.GetType(opdata.resultType);
RDCASSERT(img.type == VarType::ReadOnlyResource || img.type == VarType::ReadWriteResource);
RDCASSERT(sampler.type == VarType::Unknown || sampler.type == VarType::ReadOnlyResource ||
sampler.type == VarType::Sampler);
// at setup time we stored the texture type for easy access here
DebugAPIWrapper::TextureType texType =
(DebugAPIWrapper::TextureType)img.value.uv[TextureTypeVariableSlot];
ShaderVariable result;
result.type = resultType.scalar().Type();
BindpointIndex samplerIndex = BindpointIndex(-1, -1, ~0U);
if(sampler.type == VarType::Sampler || sampler.type == VarType::ReadOnlyResource)
samplerIndex = sampler.GetBinding();
if(!debugger.GetAPIWrapper()->CalculateSampleGather(
*this, opdata.op, texType, img.GetBinding(), samplerIndex, uv, ddxCalc, ddyCalc,
compare, gather, operands, result))
{
// sample failed. Pretend we got 0 columns back
result.value.u.x = 0;
result.value.u.y = 0;
result.value.u.z = 0;
if(result.type == VarType::Float || result.type == VarType::Half)
result.value.f.w = 1.0f;
else if(result.type == VarType::Double)
result.value.d.w = 1.0;
else
result.value.u.w = 1;
}
result.rows = 1;
result.columns = RDCMAX(1U, resultType.vector().count);
SetDst(opdata.result, result);
break;
}
//////////////////////////////////////////////////////////////////////////////
//
// Block flow control opcodes
//
//////////////////////////////////////////////////////////////////////////////
case Op::Label:
case Op::SelectionMerge:
case Op::LoopMerge:
{
// we shouldn't process these, we should always jump past them
RDCERR("Unexpected %s", ToStr(opdata.op).c_str());
break;
}
case Op::Switch:
{
OpSwitch switch_(it);
ShaderVariable selector = GetSrc(switch_.selector);
Id targetLabel = switch_.def;
for(const PairLiteralIntegerIdRef &case_ : switch_.target)
{
if(selector.value.u.x == case_.first)
{
targetLabel = case_.second;
break;
}
}
JumpToLabel(targetLabel);
break;
}
case Op::Branch:
{
OpBranch branch(it);
JumpToLabel(branch.targetLabel);
break;
}
case Op::BranchConditional:
{
OpBranchConditional branch(it);
Id target = branch.falseLabel;
if(GetSrc(branch.condition).value.u.x)
target = branch.trueLabel;
JumpToLabel(target);
break;
}
case Op::Phi:
{
OpPhi phi(it);
ShaderVariable var;
for(const PairIdRefIdRef &parent : phi.parents)
{
if(parent.second == lastBlock)
{
var = GetSrc(parent.first);
break;
}
}
// we should have had a matching for the OpPhi of the block we came from
RDCASSERT(!var.name.empty());
SetDst(phi.result, var);
break;
}
//////////////////////////////////////////////////////////////////////////////
//
// Misc opcodes
//
//////////////////////////////////////////////////////////////////////////////
case Op::ReadClockKHR:
{
const DataType &resultType = debugger.GetType(opdata.resultType);
ShaderVariable result;
result.type = resultType.scalar().Type();
result.rows = 1;
result.columns = RDCMAX(1U, resultType.vector().count);
result.value.u64v[0] = global.clock;
SetDst(opdata.result, result);
break;
}
case Op::IsHelperInvocationEXT:
{
ShaderVariable result;
result.type = VarType::UInt;
result.rows = 1;
result.columns = 1;
result.value.u.x = helperInvocation;
SetDst(opdata.result, result);
break;
}
case Op::DemoteToHelperInvocationEXT:
{
helperInvocation = true;
break;
}
//////////////////////////////////////////////////////////////////////////////
//
// Function flow control opcodes
//
//////////////////////////////////////////////////////////////////////////////
case Op::FunctionCall:
{
OpFunctionCall call(it);
// we hit this twice. The first time we don't have a return value so we jump into the
// function. The second time we do have a return value so we process it and continue
if(returnValue.name.empty())
{
uint32_t returnInstruction = nextInstruction - 1;
nextInstruction = debugger.GetInstructionForFunction(call.function);
EnterFunction(call.arguments);
RDCASSERT(callstack.back()->function == call.function);
callstack.back()->funcCallInstruction = returnInstruction;
}
else
{
SetDst(call.result, returnValue);
returnValue.name.clear();
}
break;
}
case Op::Kill:
{
killed = true;
// destroy all stack frames
for(StackFrame *exitingFrame : callstack)
delete exitingFrame;
callstack.clear();
break;
}
case Op::Return:
case Op::ReturnValue:
{
StackFrame *exitingFrame = callstack.back();
callstack.pop_back();
if(callstack.empty())
{
// if there's no callstack there's no return address, jump to the function end
it++; // see what the next instruction is
// keep going until it's the end of the function
while(OpDecoder(it).op != Op::FunctionEnd)
{
nextInstruction++;
it++;
}
}
else
{
returnValue.name = "<return value>";
if(opdata.op == Op::ReturnValue)
{
OpReturnValue ret(it);
returnValue = GetSrc(ret.value);
}
nextInstruction = exitingFrame->funcCallInstruction;
// process the outgoing and incoming scopes
ProcessScopeChange(live, callstack.back()->live);
// restore the live list from the calling frame
live = callstack.back()->live;
sourceVars = callstack.back()->sourceVars;
}
delete exitingFrame;
break;
}
//////////////////////////////////////////////////////////////////////////////
//
// Misc. opcodes
//
//////////////////////////////////////////////////////////////////////////////
case Op::Undef:
{
// this was processed as a constant, since it can appear in the constants section as well as
// in blocks. Just assign the value to itself so that it shows up as a change
OpUndef undef(it);
SetDst(undef.result, GetSrc(undef.result));
break;
}
case Op::Nop:
{
// nothing to do
break;
}
case Op::SourceContinued:
case Op::Source:
case Op::SourceExtension:
case Op::Name:
case Op::MemberName:
case Op::String:
case Op::Extension:
case Op::ExtInstImport:
case Op::MemoryModel:
case Op::EntryPoint:
case Op::ExecutionMode:
case Op::Capability:
case Op::TypeVoid:
case Op::TypeBool:
case Op::TypeInt:
case Op::TypeFloat:
case Op::TypeVector:
case Op::TypeMatrix:
case Op::TypeImage:
case Op::TypeSampler:
case Op::TypeSampledImage:
case Op::TypeArray:
case Op::TypeRuntimeArray:
case Op::TypeStruct:
case Op::TypeOpaque:
case Op::TypePointer:
case Op::TypeFunction:
case Op::TypeEvent:
case Op::TypeDeviceEvent:
case Op::TypeReserveId:
case Op::TypeQueue:
case Op::TypePipe:
case Op::TypeForwardPointer:
case Op::ConstantTrue:
case Op::ConstantFalse:
case Op::Constant:
case Op::ConstantComposite:
case Op::ConstantSampler:
case Op::ConstantNull:
case Op::SpecConstantTrue:
case Op::SpecConstantFalse:
case Op::SpecConstant:
case Op::SpecConstantComposite:
case Op::SpecConstantOp:
case Op::Decorate:
case Op::MemberDecorate:
case Op::DecorationGroup:
case Op::GroupDecorate:
case Op::GroupMemberDecorate:
case Op::Unreachable:
case Op::DecorateString:
case Op::MemberDecorateString:
case Op::DecorateId:
case Op::ModuleProcessed:
case Op::ExecutionModeId:
{
RDCERR("Encountered unexpected global SPIR-V operation %s", ToStr(opdata.op).c_str());
break;
}
case Op::GenericPtrMemSemantics:
case Op::ImageQueryFormat:
case Op::ImageQueryOrder:
case Op::SatConvertSToU:
case Op::SatConvertUToS:
case Op::PtrCastToGeneric:
case Op::GenericCastToPtr:
case Op::GenericCastToPtrExplicit:
case Op::SizeOf:
case Op::CopyMemorySized:
case Op::IsFinite:
case Op::IsNormal:
case Op::SignBitSet:
case Op::LessOrGreater:
case Op::Ordered:
case Op::Unordered:
case Op::LifetimeStart:
case Op::LifetimeStop:
case Op::AtomicCompareExchangeWeak:
case Op::AtomicFlagTestAndSet:
case Op::AtomicFlagClear:
case Op::GroupAsyncCopy:
case Op::GroupWaitEvents:
case Op::GetKernelLocalSizeForSubgroupCount:
case Op::GetKernelMaxNumSubgroups:
case Op::EnqueueMarker:
case Op::EnqueueKernel:
case Op::GetKernelNDrangeSubGroupCount:
case Op::GetKernelNDrangeMaxSubGroupSize:
case Op::GetKernelWorkGroupSize:
case Op::GetKernelPreferredWorkGroupSizeMultiple:
case Op::RetainEvent:
case Op::ReleaseEvent:
case Op::CreateUserEvent:
case Op::IsValidEvent:
case Op::SetUserEventStatus:
case Op::CaptureEventProfilingInfo:
case Op::GetDefaultQueue:
case Op::BuildNDRange:
case Op::TypeNamedBarrier:
case Op::NamedBarrierInitialize:
case Op::MemoryNamedBarrier:
case Op::ReadPipe:
case Op::WritePipe:
case Op::ReservedReadPipe:
case Op::ReservedWritePipe:
case Op::ReserveReadPipePackets:
case Op::ReserveWritePipePackets:
case Op::CommitReadPipe:
case Op::CommitWritePipe:
case Op::IsValidReserveId:
case Op::GetNumPipePackets:
case Op::GetMaxPipePackets:
case Op::GroupReserveReadPipePackets:
case Op::GroupReserveWritePipePackets:
case Op::GroupCommitReadPipe:
case Op::GroupCommitWritePipe:
case Op::TypePipeStorage:
case Op::ConstantPipeStorage:
case Op::CreatePipeFromPipeStorage:
{
// these are kernel only
RDCERR("Encountered unexpected kernel SPIR-V operation %s", ToStr(opdata.op).c_str());
break;
}
case Op::Line:
case Op::NoLine:
case Op::Function:
case Op::FunctionParameter:
case Op::FunctionEnd:
case Op::Variable:
{
// these should be handled elsewhere specially
RDCERR("Encountered SPIR-V operation %s in general dispatch loop", ToStr(opdata.op).c_str());
break;
}
case Op::Max:
default: RDCWARN("Unhandled SPIR-V operation %s", ToStr(opdata.op).c_str()); break;
}
// skip over any degenerate branches
while(true)
{
it = debugger.GetIterForInstruction(nextInstruction);
if(it.opcode() == Op::Branch)
{
Id target = OpBranch(it).targetLabel;
it++;
while(it.opcode() == Op::Line || it.opcode() == Op::NoLine)
it++;
if(target == OpLabel(it).result)
{
JumpToLabel(target);
continue;
}
}
break;
}
// set the state's next instruction (if we have one) to ours, bounded by how many
// instructions there are
if(m_State)
m_State->nextInstruction = RDCMIN(nextInstruction, debugger.GetNumInstructions() - 1);
m_State = NULL;
}
}; // namespace rdcspv