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