Pipeline: Implement the SpirvShader debugger.
Generate a synthetic file containing the spirv disassembly, and allow the debugger to single line step over these instructions, along with inspection of the SSA values.
All of this is no-op unless ENABLE_VK_DEBUGGER is defined at compile time, and the VK_DEBUG_PORT env var is set at run time.
Bug: b/145351270
Change-Id: Iba71717d78f7213ba586a1632b44f5fe08addf08
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/38915
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/Pipeline/BUILD.gn b/src/Pipeline/BUILD.gn
index cf74d42..00747b5 100644
--- a/src/Pipeline/BUILD.gn
+++ b/src/Pipeline/BUILD.gn
@@ -41,6 +41,7 @@
"SpirvShader.cpp",
"SpirvShaderArithmetic.cpp",
"SpirvShaderControlFlow.cpp",
+ "SpirvShaderDebugger.cpp",
"SpirvShaderEnumNames.cpp",
"SpirvShaderGLSLstd450.cpp",
"SpirvShaderGroup.cpp",
diff --git a/src/Pipeline/SpirvShader.cpp b/src/Pipeline/SpirvShader.cpp
index be6125e..0df1cfd 100644
--- a/src/Pipeline/SpirvShader.cpp
+++ b/src/Pipeline/SpirvShader.cpp
@@ -18,6 +18,8 @@
#include "Vulkan/VkPipelineLayout.hpp"
#include "Vulkan/VkRenderPass.hpp"
+#include "marl/defer.h"
+
#include <spirv/unified1/spirv.hpp>
namespace sw {
@@ -39,6 +41,11 @@
{
ASSERT(insns.size() > 0);
+ if(dbgctx)
+ {
+ dbgInit(dbgctx);
+ }
+
if(renderPass)
{
// capture formats of any input attachments present
@@ -719,6 +726,13 @@
{
it.second.AssignBlockFields();
}
+
+ dbgCreateFile();
+}
+
+SpirvShader::~SpirvShader()
+{
+ dbgTerm();
}
void SpirvShader::DeclareType(InsnIterator insn)
@@ -1452,6 +1466,7 @@
}
object.definition = insn;
+ dbgDeclareResult(insn, resultId);
}
OutOfBoundsBehavior SpirvShader::EmitState::getOutOfBoundsBehavior(spv::StorageClass storageClass) const
@@ -1542,6 +1557,9 @@
{
EmitState state(routine, entryPoint, activeLaneMask, storesAndAtomicsMask, descriptorSets, robustBufferAccess, executionModel);
+ dbgBeginEmit(&state);
+ defer(dbgEndEmit(&state));
+
// Emit everything up to the first label
// TODO: Separate out dispatch of block from non-block instructions?
for(auto insn : *this)
@@ -1577,6 +1595,9 @@
SpirvShader::EmitResult SpirvShader::EmitInstruction(InsnIterator insn, EmitState *state) const
{
+ dbgBeginEmitInstruction(insn, state);
+ defer(dbgEndEmitInstruction(insn, state));
+
auto opcode = insn.opcode();
switch(opcode)
@@ -1624,7 +1645,6 @@
case spv::OpSource:
case spv::OpSourceContinued:
case spv::OpSourceExtension:
- case spv::OpLine:
case spv::OpNoLine:
case spv::OpModuleProcessed:
case spv::OpString:
@@ -1632,6 +1652,9 @@
// or don't require any work at all.
return EmitResult::Continue;
+ case spv::OpLine:
+ return EmitLine(insn, state);
+
case spv::OpLabel:
return EmitResult::Continue;
diff --git a/src/Pipeline/SpirvShader.hpp b/src/Pipeline/SpirvShader.hpp
index ea5bb61..d46f760 100644
--- a/src/Pipeline/SpirvShader.hpp
+++ b/src/Pipeline/SpirvShader.hpp
@@ -497,6 +497,8 @@
bool robustBufferAccess,
const std::shared_ptr<vk::dbg::Context> &dbgctx);
+ ~SpirvShader();
+
struct Modes
{
bool EarlyFragmentTests : 1;
@@ -1090,6 +1092,7 @@
EmitResult EmitSelect(InsnIterator insn, EmitState *state) const;
EmitResult EmitExtendedInstruction(InsnIterator insn, EmitState *state) const;
EmitResult EmitExtGLSLstd450(InsnIterator insn, EmitState *state) const;
+ EmitResult EmitLine(InsnIterator insn, EmitState *state) const;
EmitResult EmitAny(InsnIterator insn, EmitState *state) const;
EmitResult EmitAll(InsnIterator insn, EmitState *state) const;
EmitResult EmitBranch(InsnIterator insn, EmitState *state) const;
@@ -1170,14 +1173,58 @@
// Returns 0 when invalid.
static VkShaderStageFlagBits executionModelToStage(spv::ExecutionModel model);
- // Impl holds private forward declaration structs that are implemented
- // in the corresponding SpirvShaderXXX.cpp files.
+ // Debugger API functions. When ENABLE_VK_DEBUGGER is not defined, these
+ // are all no-ops.
+
+ // dbgInit() initializes the debugger code generation.
+ // All other dbgXXX() functions are no-op until this is called.
+ void dbgInit(const std::shared_ptr<vk::dbg::Context> &dbgctx);
+
+ // dbgTerm() terminates the debugger code generation.
+ void dbgTerm();
+
+ // dbgCreateFile() generates a synthetic file containing the disassembly
+ // of the SPIR-V shader. This is the file displayed in the debug
+ // session.
+ void dbgCreateFile();
+
+ // dbgBeginEmit() sets up the debugging state for the shader.
+ void dbgBeginEmit(EmitState *state) const;
+
+ // dbgEndEmit() tears down the debugging state for the shader.
+ void dbgEndEmit(EmitState *state) const;
+
+ // dbgBeginEmitInstruction() updates the current debugger location for
+ // the given instruction.
+ void dbgBeginEmitInstruction(InsnIterator insn, EmitState *state) const;
+
+ // dbgEndEmitInstruction() creates any new debugger variables for the
+ // instruction that just completed.
+ void dbgEndEmitInstruction(InsnIterator insn, EmitState *state) const;
+
+ // dbgExposeIntermediate() exposes the intermediate with the given ID to
+ // the debugger.
+ void dbgExposeIntermediate(Object::ID id, EmitState *state) const;
+
+ // dbgUpdateActiveLaneMask() updates the active lane masks to the
+ // debugger.
+ void dbgUpdateActiveLaneMask(RValue<SIMD::Int> mask, EmitState *state) const;
+
+ // dbgDeclareResult() associates resultId as the result of the given
+ // instruction.
+ void dbgDeclareResult(const InsnIterator &insn, Object::ID resultId) const;
+
+ // Impl holds forward declaration structs and pointers to state for the
+ // private implementations in the corresponding SpirvShaderXXX.cpp files.
// This allows access to the private members of the SpirvShader, without
// littering the header with implementation details.
struct Impl
{
+ struct Debugger;
struct Group;
+ Debugger *debugger = nullptr;
};
+ Impl impl;
};
class SpirvRoutine
@@ -1230,6 +1277,8 @@
std::array<SIMD::Int, 3> localInvocationID;
std::array<SIMD::Int, 3> globalInvocationID;
+ Pointer<Byte> dbgState; // Pointer to a debugger state.
+
void createVariable(SpirvShader::Object::ID id, uint32_t size)
{
bool added = variables.emplace(id, Variable(size)).second;
diff --git a/src/Pipeline/SpirvShaderControlFlow.cpp b/src/Pipeline/SpirvShaderControlFlow.cpp
index 7826415..824e468 100644
--- a/src/Pipeline/SpirvShaderControlFlow.cpp
+++ b/src/Pipeline/SpirvShaderControlFlow.cpp
@@ -702,6 +702,7 @@
void SpirvShader::SetActiveLaneMask(RValue<SIMD::Int> mask, EmitState *state) const
{
state->activeLaneMaskValue = mask.value;
+ dbgUpdateActiveLaneMask(mask, state);
}
} // namespace sw
\ No newline at end of file
diff --git a/src/Pipeline/SpirvShaderDebugger.cpp b/src/Pipeline/SpirvShaderDebugger.cpp
new file mode 100644
index 0000000..ab596f7
--- /dev/null
+++ b/src/Pipeline/SpirvShaderDebugger.cpp
@@ -0,0 +1,686 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "SpirvShader.hpp"
+
+#ifdef ENABLE_VK_DEBUGGER
+
+# include "Vulkan/Debug/Context.hpp"
+# include "Vulkan/Debug/File.hpp"
+# include "Vulkan/Debug/Thread.hpp"
+# include "Vulkan/Debug/Variable.hpp"
+
+# include "spirv-tools/libspirv.h"
+
+# include <algorithm>
+
+namespace spvtools {
+
+// Function implemented in third_party/SPIRV-Tools/source/disassemble.cpp
+// but with no public header.
+// This is a C++ function, so the name is mangled, and signature changes will
+// result in a linker error instead of runtime signature mismatches.
+extern std::string spvInstructionBinaryToText(const spv_target_env env,
+ const uint32_t *inst_binary,
+ const size_t inst_word_count,
+ const uint32_t *binary,
+ const size_t word_count,
+ const uint32_t options);
+
+} // namespace spvtools
+
+namespace {
+
+const char *laneNames[] = { "Lane 0", "Lane 1", "Lane 2", "Lane 3" };
+static_assert(sizeof(laneNames) / sizeof(laneNames[0]) == sw::SIMD::Width,
+ "laneNames must have SIMD::Width entries");
+
+template<typename T>
+std::string tostring(const T &s)
+{
+ return std::to_string(s);
+}
+std::string tostring(const char *s)
+{
+ return s;
+}
+std::string tostring(sw::SpirvShader::Object::ID id)
+{
+ return "%" + std::to_string(id.value());
+}
+
+} // anonymous namespace
+
+namespace rr {
+
+////////////////////////////////////////////////////////////////////////////////
+// rr::CToReactor<T> specializations.
+////////////////////////////////////////////////////////////////////////////////
+template<typename T>
+struct CToReactor<sw::SpirvID<T>>
+{
+ using type = rr::Int;
+ static rr::Int cast(sw::SpirvID<T> id) { return rr::Int(id.value()); }
+};
+
+template<typename T>
+struct CToReactor<vk::dbg::ID<T>>
+{
+ using type = rr::Int;
+ static rr::Int cast(vk::dbg::ID<T> id) { return rr::Int(id.value()); }
+};
+
+} // namespace rr
+
+namespace sw {
+
+////////////////////////////////////////////////////////////////////////////////
+// sw::SpirvShader::Impl::Debugger
+//
+// Private struct holding debugger information for the SpirvShader.
+////////////////////////////////////////////////////////////////////////////////
+struct SpirvShader::Impl::Debugger
+{
+ class Group;
+ class State;
+
+ void setPosition(EmitState *state, const std::string &path, uint32_t line, uint32_t column);
+
+ // exposeVariable exposes the variable with the given ID to the debugger
+ // using the specified key.
+ template<typename Key>
+ void exposeVariable(
+ const SpirvShader *shader,
+ const Key &key,
+ Object::ID id,
+ EmitState *state) const;
+
+ // exposeVariable exposes the variable with the given ID to the
+ // debugger under the specified group, for the specified SIMD lane.
+ template<typename Key>
+ void exposeVariable(
+ const SpirvShader *shader,
+ const Group &group,
+ int lane,
+ const Key &key,
+ Object::ID id,
+ EmitState *state,
+ int wordOffset = 0) const;
+
+ std::shared_ptr<vk::dbg::Context> ctx;
+ std::shared_ptr<vk::dbg::File> spirvFile;
+ std::unordered_map<const void *, int> spirvLineMappings; // instruction pointer to line
+ std::unordered_map<const void *, Object::ID> results; // instruction pointer to result ID
+
+private:
+ std::unordered_map<std::string, vk::dbg::File::ID> fileIDs;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// sw::SpirvShader::Impl::Debugger::State
+//
+// State holds the runtime data structures for the shader debug session.
+////////////////////////////////////////////////////////////////////////////////
+class SpirvShader::Impl::Debugger::State
+{
+public:
+ static State *create(const Debugger *debugger, const char *name);
+ static void destroy(State *);
+
+ State(const Debugger *debugger, const char *stackBase, vk::dbg::Context::Lock &lock);
+ ~State();
+
+ void enter(vk::dbg::Context::Lock &lock, const char *name);
+ void exit();
+ void updateActiveLaneMask(int lane, bool enabled);
+ void update(vk::dbg::File::ID file, int line, int column);
+
+ vk::dbg::VariableContainer *locals();
+ vk::dbg::VariableContainer *hovers();
+ vk::dbg::VariableContainer *localsLane(int lane);
+
+ template<typename K>
+ vk::dbg::VariableContainer *group(vk::dbg::VariableContainer *vc, K key);
+
+ template<typename K, typename V>
+ void putVal(vk::dbg::VariableContainer *vc, K key, V value);
+
+ struct Scopes
+ {
+ std::shared_ptr<vk::dbg::Scope> locals;
+ std::shared_ptr<vk::dbg::Scope> hovers;
+ std::array<std::shared_ptr<vk::dbg::VariableContainer>, sw::SIMD::Width> localsByLane;
+ };
+
+ const Debugger *debugger;
+ const std::shared_ptr<vk::dbg::Thread> thread;
+ Scopes threadScopes;
+};
+
+SpirvShader::Impl::Debugger::State *SpirvShader::Impl::Debugger::State::create(const Debugger *debugger, const char *name)
+{
+ auto lock = debugger->ctx->lock();
+ return new State(debugger, name, lock);
+}
+
+void SpirvShader::Impl::Debugger::State::destroy(State *state)
+{
+ delete state;
+}
+
+SpirvShader::Impl::Debugger::State::State(const Debugger *debugger, const char *stackBase, vk::dbg::Context::Lock &lock)
+ : debugger(debugger)
+ , thread(lock.currentThread())
+{
+ enter(lock, stackBase);
+ thread->update([&](vk::dbg::Frame &frame) {
+ threadScopes.locals = frame.locals;
+ threadScopes.hovers = frame.hovers;
+ for(int i = 0; i < sw::SIMD::Width; i++)
+ {
+ threadScopes.localsByLane[i] = lock.createVariableContainer();
+ frame.locals->variables->put(laneNames[i], threadScopes.localsByLane[i]);
+ }
+ });
+}
+
+SpirvShader::Impl::Debugger::State::~State()
+{
+ exit();
+}
+
+void SpirvShader::Impl::Debugger::State::enter(vk::dbg::Context::Lock &lock, const char *name)
+{
+ thread->enter(lock, debugger->spirvFile, name);
+}
+
+void SpirvShader::Impl::Debugger::State::exit()
+{
+ thread->exit();
+}
+
+void SpirvShader::Impl::Debugger::State::updateActiveLaneMask(int lane, bool enabled)
+{
+ threadScopes.localsByLane[lane]->put("enabled", vk::dbg::make_constant(enabled));
+}
+
+void SpirvShader::Impl::Debugger::State::update(vk::dbg::File::ID fileID, int line, int column)
+{
+ auto file = debugger->ctx->lock().get(fileID);
+ thread->update([&](vk::dbg::Frame &frame) {
+ frame.location = { file, line, column };
+ });
+}
+
+vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::locals()
+{
+ return threadScopes.locals->variables.get();
+}
+
+vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::hovers()
+{
+ return threadScopes.hovers->variables.get();
+}
+
+vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::localsLane(int i)
+{
+ return threadScopes.localsByLane[i].get();
+}
+
+template<typename K>
+vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::group(vk::dbg::VariableContainer *vc, K key)
+{
+ auto out = debugger->ctx->lock().createVariableContainer();
+ vc->put(tostring(key), out);
+ return out.get();
+}
+
+template<typename K, typename V>
+void SpirvShader::Impl::Debugger::State::putVal(vk::dbg::VariableContainer *vc, K key, V value)
+{
+ vc->put(tostring(key), vk::dbg::make_constant(value));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sw::SpirvShader::Impl::Debugger::Group
+//
+// This provides a convenient C++ interface for adding debugger values to
+// VariableContainers.
+////////////////////////////////////////////////////////////////////////////////
+class SpirvShader::Impl::Debugger::Group
+{
+public:
+ using Ptr = rr::Pointer<rr::Byte>;
+
+ static Group hovers(Ptr state);
+ static Group locals(Ptr state);
+ static Group localsLane(Ptr state, int lane);
+
+ Group(Ptr state, Ptr group);
+
+ template<typename K, typename RK>
+ Group group(RK key) const;
+
+ template<typename K, typename V, typename RK, typename RV>
+ void put(RK key, RV value) const;
+
+ template<typename K, typename V, typename RK, typename RV>
+ void put(RK key, RV x, RV y) const;
+
+ template<typename K, typename V, typename RK, typename RV>
+ void put(RK key, RV x, RV y, RV z) const;
+
+ template<typename K, typename V, typename RK, typename RV>
+ void put(RK key, RV x, RV y, RV z, RV w) const;
+
+ template<typename K, typename V, typename VEC>
+ void putVec3(K key, const VEC &v) const;
+
+private:
+ Ptr state;
+ Ptr ptr;
+};
+
+SpirvShader::Impl::Debugger::Group
+SpirvShader::Impl::Debugger::Group::hovers(Ptr state)
+{
+ return Group(state, rr::Call(&State::hovers, state));
+}
+
+SpirvShader::Impl::Debugger::Group
+SpirvShader::Impl::Debugger::Group::locals(Ptr state)
+{
+ return Group(state, rr::Call(&State::locals, state));
+}
+
+SpirvShader::Impl::Debugger::Group
+SpirvShader::Impl::Debugger::Group::localsLane(Ptr state, int lane)
+{
+ return Group(state, rr::Call(&State::localsLane, state, lane));
+}
+
+SpirvShader::Impl::Debugger::Group::Group(Ptr state, Ptr group)
+ : state(state)
+ , ptr(group)
+{}
+
+template<typename K, typename RK>
+SpirvShader::Impl::Debugger::Group SpirvShader::Impl::Debugger::Group::group(RK key) const
+{
+ return Group(state, rr::Call(&State::group<K>, state, ptr, key));
+}
+
+template<typename K, typename V, typename RK, typename RV>
+void SpirvShader::Impl::Debugger::Group::put(RK key, RV value) const
+{
+ rr::Call(&State::putVal<K, V>, state, ptr, key, value);
+}
+
+template<typename K, typename V, typename RK, typename RV>
+void SpirvShader::Impl::Debugger::Group::put(RK key, RV x, RV y) const
+{
+ auto vec = group<K>(key);
+ vec.template put<const char *, V>("x", x);
+ vec.template put<const char *, V>("y", y);
+}
+
+template<typename K, typename V, typename RK, typename RV>
+void SpirvShader::Impl::Debugger::Group::put(RK key, RV x, RV y, RV z) const
+{
+ auto vec = group<K>(key);
+ vec.template put<const char *, V>("x", x);
+ vec.template put<const char *, V>("y", y);
+ vec.template put<const char *, V>("z", z);
+}
+
+template<typename K, typename V, typename RK, typename RV>
+void SpirvShader::Impl::Debugger::Group::put(RK key, RV x, RV y, RV z, RV w) const
+{
+ auto vec = group<K>(key);
+ vec.template put<const char *, V>("x", x);
+ vec.template put<const char *, V>("y", y);
+ vec.template put<const char *, V>("z", z);
+ vec.template put<const char *, V>("w", w);
+}
+
+template<typename K, typename V, typename VEC>
+void SpirvShader::Impl::Debugger::Group::putVec3(K key, const VEC &v) const
+{
+ auto vec = group<K>(key);
+ vec.template put<const char *, V>("x", Extract(v, 0));
+ vec.template put<const char *, V>("y", Extract(v, 1));
+ vec.template put<const char *, V>("z", Extract(v, 2));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sw::SpirvShader::Impl::Debugger methods
+////////////////////////////////////////////////////////////////////////////////
+void SpirvShader::Impl::Debugger::setPosition(EmitState *state, const std::string &path, uint32_t line, uint32_t column)
+{
+ auto it = fileIDs.find(path);
+ if(it != fileIDs.end())
+ {
+ rr::Call(&State::update, state->routine->dbgState, it->second, line, column);
+ }
+}
+
+template<typename Key>
+void SpirvShader::Impl::Debugger::exposeVariable(
+ const SpirvShader *shader,
+ const Key &key,
+ Object::ID id,
+ EmitState *state) const
+{
+ auto dbgState = state->routine->dbgState;
+ auto hover = Group::hovers(dbgState).group<Key>(key);
+ for(int lane = 0; lane < SIMD::Width; lane++)
+ {
+ exposeVariable(shader, Group::localsLane(dbgState, lane), lane, key, id, state);
+ exposeVariable(shader, hover, lane, laneNames[lane], id, state);
+ }
+}
+
+template<typename Key>
+void SpirvShader::Impl::Debugger::exposeVariable(
+ const SpirvShader *shader,
+ const Group &group,
+ int l,
+ const Key &key,
+ Object::ID id,
+ EmitState *state,
+ int wordOffset /* = 0 */) const
+{
+ GenericValue val(shader, state, id);
+ switch(shader->getType(val.type).opcode())
+ {
+ case spv::OpTypeInt:
+ {
+ group.put<Key, int>(key, Extract(val.Int(0), l));
+ }
+ break;
+ case spv::OpTypeFloat:
+ {
+ group.put<Key, float>(key, Extract(val.Float(0), l));
+ }
+ break;
+ case spv::OpTypeVector:
+ {
+ auto count = shader->getType(val.type).definition.word(3);
+ switch(count)
+ {
+ case 1:
+ group.put<Key, float>(key, Extract(val.Float(0), l));
+ break;
+ case 2:
+ group.put<Key, float>(key, Extract(val.Float(0), l), Extract(val.Float(1), l));
+ break;
+ case 3:
+ group.put<Key, float>(key, Extract(val.Float(0), l), Extract(val.Float(1), l), Extract(val.Float(2), l));
+ break;
+ case 4:
+ group.put<Key, float>(key, Extract(val.Float(0), l), Extract(val.Float(1), l), Extract(val.Float(2), l), Extract(val.Float(3), l));
+ break;
+ default:
+ {
+ auto vec = group.group<Key>(key);
+ for(uint32_t i = 0; i < count; i++)
+ {
+ vec.template put<int, float>(i, Extract(val.Float(i), l));
+ }
+ }
+ break;
+ }
+ }
+ break;
+ case spv::OpTypePointer:
+ {
+ auto objectTy = shader->getType(shader->getObject(id).type);
+ bool interleavedByLane = IsStorageInterleavedByLane(objectTy.storageClass);
+ auto ptr = state->getPointer(id);
+ auto ptrGroup = group.group<Key>(key);
+ shader->VisitMemoryObject(id, [&](const MemoryElement &el) {
+ auto p = ptr + el.offset;
+ if(interleavedByLane) { p = InterleaveByLane(p); } // TODO: Interleave once, then add offset?
+ auto simd = p.Load<SIMD::Float>(sw::OutOfBoundsBehavior::Nullify, state->activeLaneMask());
+ ptrGroup.template put<int, float>(el.index, Extract(simd, l));
+ });
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sw::SpirvShader
+////////////////////////////////////////////////////////////////////////////////
+void SpirvShader::dbgInit(const std::shared_ptr<vk::dbg::Context> &dbgctx)
+{
+ impl.debugger = new Impl::Debugger();
+ impl.debugger->ctx = dbgctx;
+}
+
+void SpirvShader::dbgTerm()
+{
+ if(impl.debugger)
+ {
+ delete impl.debugger;
+ }
+}
+
+void SpirvShader::dbgCreateFile()
+{
+ auto dbg = impl.debugger;
+ if(!dbg) { return; }
+
+ int currentLine = 1;
+ std::string source;
+ for(auto insn : *this)
+ {
+ auto instruction = spvtools::spvInstructionBinaryToText(
+ SPV_ENV_VULKAN_1_1,
+ insn.wordPointer(0),
+ insn.wordCount(),
+ insns.data(),
+ insns.size(),
+ SPV_BINARY_TO_TEXT_OPTION_NO_HEADER) +
+ "\n";
+ dbg->spirvLineMappings[insn.wordPointer(0)] = currentLine;
+ currentLine += std::count(instruction.begin(), instruction.end(), '\n');
+ source += instruction;
+ }
+ std::string name;
+ switch(executionModel)
+ {
+ case spv::ExecutionModelVertex: name = "VertexShader"; break;
+ case spv::ExecutionModelFragment: name = "FragmentShader"; break;
+ case spv::ExecutionModelGLCompute: name = "ComputeShader"; break;
+ default: name = "SPIR-V Shader"; break;
+ }
+ static std::atomic<int> id = { 0 };
+ name += std::to_string(id++) + ".spvasm";
+ dbg->spirvFile = dbg->ctx->lock().createVirtualFile(name.c_str(), source.c_str());
+}
+
+void SpirvShader::dbgBeginEmit(EmitState *state) const
+{
+ auto dbg = impl.debugger;
+ if(!dbg) { return; }
+
+ using Group = Impl::Debugger::Group;
+
+ auto routine = state->routine;
+
+ auto type = "SPIR-V";
+ switch(executionModel)
+ {
+ case spv::ExecutionModelVertex: type = "VertexShader"; break;
+ case spv::ExecutionModelFragment: type = "FragmentShader"; break;
+ case spv::ExecutionModelGLCompute: type = "ComputeShader"; break;
+ default: type = "SPIR-V Shader"; break;
+ }
+ auto dbgState = rr::Call(&Impl::Debugger::State::create, dbg, type);
+
+ routine->dbgState = dbgState;
+
+ SetActiveLaneMask(state->activeLaneMask(), state);
+
+ auto locals = Group::locals(dbgState);
+ locals.put<const char *, int>("subgroupSize", routine->invocationsPerSubgroup);
+
+ switch(executionModel)
+ {
+ case spv::ExecutionModelGLCompute:
+ locals.putVec3<const char *, int>("numWorkgroups", routine->numWorkgroups);
+ locals.putVec3<const char *, int>("workgroupID", routine->workgroupID);
+ locals.putVec3<const char *, int>("workgroupSize", routine->workgroupSize);
+ locals.put<const char *, int>("numSubgroups", routine->subgroupsPerWorkgroup);
+ locals.put<const char *, int>("subgroupIndex", routine->subgroupIndex);
+
+ for(int i = 0; i < SIMD::Width; i++)
+ {
+ auto lane = Group::localsLane(dbgState, i);
+ lane.put<const char *, int>("globalInvocationId",
+ rr::Extract(routine->globalInvocationID[0], i),
+ rr::Extract(routine->globalInvocationID[1], i),
+ rr::Extract(routine->globalInvocationID[2], i));
+ lane.put<const char *, int>("localInvocationId",
+ rr::Extract(routine->localInvocationID[0], i),
+ rr::Extract(routine->localInvocationID[1], i),
+ rr::Extract(routine->localInvocationID[2], i));
+ lane.put<const char *, int>("localInvocationIndex", rr::Extract(routine->localInvocationIndex, i));
+ }
+ break;
+
+ case spv::ExecutionModelFragment:
+ locals.put<const char *, int>("viewIndex", routine->viewID);
+ for(int i = 0; i < SIMD::Width; i++)
+ {
+ auto lane = Group::localsLane(dbgState, i);
+ lane.put<const char *, float>("fragCoord",
+ rr::Extract(routine->fragCoord[0], i),
+ rr::Extract(routine->fragCoord[1], i),
+ rr::Extract(routine->fragCoord[2], i),
+ rr::Extract(routine->fragCoord[3], i));
+ lane.put<const char *, float>("pointCoord",
+ rr::Extract(routine->pointCoord[0], i),
+ rr::Extract(routine->pointCoord[1], i));
+ lane.put<const char *, int>("windowSpacePosition",
+ rr::Extract(routine->windowSpacePosition[0], i),
+ rr::Extract(routine->windowSpacePosition[1], i));
+ lane.put<const char *, int>("helperInvocation", rr::Extract(routine->helperInvocation, i));
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+void SpirvShader::dbgEndEmit(EmitState *state) const
+{
+ auto dbg = impl.debugger;
+ if(!dbg) { return; }
+
+ rr::Call(&Impl::Debugger::State::destroy, state->routine->dbgState);
+}
+
+void SpirvShader::dbgBeginEmitInstruction(InsnIterator insn, EmitState *state) const
+{
+ auto dbg = impl.debugger;
+ if(!dbg) { return; }
+
+ auto line = dbg->spirvLineMappings.at(insn.wordPointer(0));
+ auto column = 0;
+ rr::Call(&Impl::Debugger::State::update, state->routine->dbgState, dbg->spirvFile->id, line, column);
+}
+
+void SpirvShader::dbgEndEmitInstruction(InsnIterator insn, EmitState *state) const
+{
+ auto dbg = impl.debugger;
+ if(!dbg) { return; }
+
+ auto resIt = dbg->results.find(insn.wordPointer(0));
+ if(resIt != dbg->results.end())
+ {
+ auto id = resIt->second;
+ dbgExposeIntermediate(id, state);
+ }
+}
+
+void SpirvShader::dbgExposeIntermediate(Object::ID id, EmitState *state) const
+{
+ auto dbg = impl.debugger;
+ if(!dbg) { return; }
+
+ dbg->exposeVariable(this, id, id, state);
+}
+
+void SpirvShader::dbgUpdateActiveLaneMask(RValue<SIMD::Int> mask, EmitState *state) const
+{
+ auto dbg = impl.debugger;
+ if(!dbg) { return; }
+
+ for(int lane = 0; lane < SIMD::Width; lane++)
+ {
+ rr::Call(&Impl::Debugger::State::updateActiveLaneMask, state->routine->dbgState, lane, rr::Extract(mask, lane) != 0);
+ }
+}
+
+void SpirvShader::dbgDeclareResult(const InsnIterator &insn, Object::ID resultId) const
+{
+ auto dbg = impl.debugger;
+ if(!dbg) { return; }
+
+ dbg->results.emplace(insn.wordPointer(0), resultId);
+}
+
+SpirvShader::EmitResult SpirvShader::EmitLine(InsnIterator insn, EmitState *state) const
+{
+ if(auto dbg = impl.debugger)
+ {
+ auto path = getString(insn.word(1));
+ auto line = insn.word(2);
+ auto column = insn.word(3);
+ dbg->setPosition(state, path, line, column);
+ }
+ return EmitResult::Continue;
+}
+
+} // namespace sw
+
+#else // ENABLE_VK_DEBUGGER
+
+// Stub implementations of the dbgXXX functions.
+namespace sw {
+
+void SpirvShader::dbgInit(const std::shared_ptr<vk::dbg::Context> &dbgctx) {}
+void SpirvShader::dbgTerm() {}
+void SpirvShader::dbgCreateFile() {}
+void SpirvShader::dbgBeginEmit(EmitState *state) const {}
+void SpirvShader::dbgEndEmit(EmitState *state) const {}
+void SpirvShader::dbgBeginEmitInstruction(InsnIterator insn, EmitState *state) const {}
+void SpirvShader::dbgEndEmitInstruction(InsnIterator insn, EmitState *state) const {}
+void SpirvShader::dbgExposeIntermediate(Object::ID id, EmitState *state) const {}
+void SpirvShader::dbgUpdateActiveLaneMask(RValue<SIMD::Int> mask, EmitState *state) const {}
+void SpirvShader::dbgDeclareResult(const InsnIterator &insn, Object::ID resultId) const {}
+
+SpirvShader::EmitResult SpirvShader::EmitLine(InsnIterator insn, EmitState *state) const
+{
+ return EmitResult::Continue;
+}
+
+} // namespace sw
+
+#endif // ENABLE_VK_DEBUGGER