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