SpirvShaderDebugger: Mark II

This is a major reworking of the way the debugger is implemented.

Instead of generating shader code that is continually driving `vk::dbg` state, the debugger now:
* Maintains a full shadow copy of all Intermediate values (SSA)
* Only calls out to C++ whenever a trap is set
* Only constructs and updates the `vk::dbg` state when a trap is hit

The main goal of this reworking is to properly support OpenCL.Debug.100's DebugValue instructions that may use deref expressions
(See https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#_debug_operations_a_id_operation_a)
These are remarkably hard to implement without having everything backed with real memory.

The resulting reimplementation is:
* Much, much faster (>1000x faster for sample apps I've tried). Only ~2x slower than non-debug enabled SwiftShader now.
* Uses much less runtime memory.
* Much cleaner, removing a load of template magic for setting variable state that was extremely hard to follow
* More thoroughly commented, explaining each component and method.

Bug: b/145351270
Change-Id: I1b48e9defae095109ebfaf90dfb7c7e22d335816
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/48729
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
diff --git a/src/Pipeline/SpirvShaderDebugger.cpp b/src/Pipeline/SpirvShaderDebugger.cpp
index 76f1fad..4aa1059 100644
--- a/src/Pipeline/SpirvShaderDebugger.cpp
+++ b/src/Pipeline/SpirvShaderDebugger.cpp
@@ -14,6 +14,8 @@
 
 #include "SpirvShader.hpp"
 
+#include "System/Types.hpp"
+
 // If enabled, each instruction will be printed before defining.
 #define PRINT_EACH_DEFINED_DBG_INSTRUCTION 0
 // If enabled, each instruction will be printed before emitting.
@@ -30,6 +32,7 @@
 #	include "Vulkan/Debug/File.hpp"
 #	include "Vulkan/Debug/Thread.hpp"
 #	include "Vulkan/Debug/Variable.hpp"
+#	include "Vulkan/Debug/EventListener.hpp"
 
 #	include "spirv/unified1/OpenCLDebugInfo100.h"
 #	include "spirv-tools/libspirv.h"
@@ -37,8 +40,137 @@
 #	include <algorithm>
 #	include <queue>
 
+////////////////////////////////////////////////////////////////////////////////
+// namespace sw::SIMD
+// Adds sw::SIMD::PerLane<> and typedefs for C++ versions of the Reactor SIMD
+// types (sw::SIMD::Int, etc)
+////////////////////////////////////////////////////////////////////////////////
+namespace sw {
+namespace SIMD {
+
+// PerLane is a SIMD vector that holds N vectors of width SIMD::Width.
+// PerLane operator[] returns the elements of a single lane (a transpose of the
+// storage arrays).
+template<typename T, int N = 1>
+struct PerLane
+{
+	sw::vec<T, N> operator[](int lane) const
+	{
+		sw::vec<T, N> out;
+		for(int i = 0; i < N; i++)
+		{
+			out[i] = elements[i][lane];
+		}
+		return out;
+	}
+	std::array<sw::vec<T, Width>, N> elements;
+};
+
+template<typename T>
+struct PerLane<T, 1>
+{
+	const T &operator[](int lane) const { return data[lane]; }
+	std::array<T, Width> data;
+};
+
+using uint_t = PerLane<unsigned int>;
+using uint2 = PerLane<unsigned int, 2>;
+using uint3 = PerLane<unsigned int, 3>;
+using uint4 = PerLane<unsigned int, 4>;
+
+using int_t = PerLane<int>;
+using int2 = PerLane<int, 2>;
+using int3 = PerLane<int, 3>;
+using int4 = PerLane<int, 4>;
+
+using float_t = PerLane<float>;
+using vec2 = PerLane<float, 2>;
+using vec3 = PerLane<float, 3>;
+using vec4 = PerLane<float, 4>;
+
+}  // namespace SIMD
+}  // namespace sw
+
+////////////////////////////////////////////////////////////////////////////////
+// namespace ::(anonymous)
+// Utility functions
+////////////////////////////////////////////////////////////////////////////////
 namespace {
 
+// vecElementName() returns the element name for the i'th vector element of
+// size n.
+// Vectors of size 4 or less use a [x,y,z,w] element naming scheme.
+// Larger vectors use a number index naming scheme.
+std::string vecElementName(int i, int n)
+{
+	return (n > 4) ? std::to_string(i) : &"x\0y\0z\0w\0"[i * 2];
+}
+
+// laneName() returns a string describing values for the lane i.
+std::string laneName(int i)
+{
+	return "Lane " + std::to_string(i);
+}
+
+// isEntryBreakpointForShaderType() returns true if name is equal to the
+// special entry breakpoint name for the given shader type.
+// This allows the IDE to request all shaders of the given type to break on
+// entry.
+bool isEntryBreakpointForShaderType(spv::ExecutionModel type, const std::string &name)
+{
+	switch(type)
+	{
+		case spv::ExecutionModelGLCompute: return name == "ComputeShader";
+		case spv::ExecutionModelFragment: return name == "FragmentShader";
+		case spv::ExecutionModelVertex: return name == "VertexShader";
+		default: return false;
+	}
+}
+
+// makeDbgValue() returns a vk::dbg::Value that contains a copy of val.
+template<typename T>
+std::shared_ptr<vk::dbg::Value> makeDbgValue(const T &val)
+{
+	return vk::dbg::make_constant(val);
+}
+
+// makeDbgValue() returns a vk::dbg::Value that contains a copy of vec.
+template<typename T, int N>
+std::shared_ptr<vk::dbg::Value> makeDbgValue(const sw::vec<T, N> &vec)
+{
+	return vk::dbg::Struct::create("vec" + std::to_string(N), [&](auto &vc) {
+		for(int i = 0; i < N; i++)
+		{
+			vc->put(vecElementName(i, N), makeDbgValue<T>(vec[i]));
+		}
+	});
+}
+
+// store() emits a store instruction to write sizeof(T) bytes from val into ptr.
+template<typename T>
+void store(const rr::RValue<rr::Pointer<rr::Byte>> &ptr, const rr::RValue<T> &val)
+{
+	*rr::Pointer<T>(ptr) = val;
+}
+
+// store() emits a store instruction to write sizeof(T) bytes from val into ptr.
+template<typename T>
+void store(const rr::RValue<rr::Pointer<rr::Byte>> &ptr, const T &val)
+{
+	*rr::Pointer<T>(ptr) = val;
+}
+
+// store() emits a store instruction to write sizeof(T) * N bytes from val into
+// ptr.
+template<typename T, std::size_t N>
+void store(const rr::RValue<rr::Pointer<rr::Byte>> &ptr, const std::array<T, N> &val)
+{
+	for(std::size_t i = 0; i < N; i++)
+	{
+		store<T>(ptr + i * sizeof(T), val[i]);
+	}
+}
+
 // ArgTy<F>::type resolves to the single argument type of the function F.
 template<typename F>
 struct ArgTy
@@ -46,23 +178,106 @@
 	using type = typename ArgTy<decltype(&F::operator())>::type;
 };
 
+// ArgTy<F>::type resolves to the single argument type of the template method.
 template<typename R, typename C, typename Arg>
 struct ArgTy<R (C::*)(Arg) const>
 {
 	using type = typename std::decay<Arg>::type;
 };
 
-template<typename T>
-using ArgTyT = typename ArgTy<T>::type;
+// ArgTyT resolves to the single argument type of the template function or
+// method F.
+template<typename F>
+using ArgTyT = typename ArgTy<F>::type;
 
-template<typename T>
-T take(std::queue<T> &queue)
+// getOrCreate() searchs the map for the given key. If the map contains an entry
+// with the given key, then the value is returned. Otherwise, create() is called
+// and the returned value is placed into the map with the given key, and this
+// value is returned.
+// create is a function with the signature:
+//   V()
+template<typename K, typename V, typename CREATE, typename HASH>
+V getOrCreate(std::unordered_map<K, V, HASH> &map, const K &key, CREATE &&create)
 {
-	auto v = queue.front();
-	queue.pop();
-	return v;
+	auto it = map.find(key);
+	if(it != map.end())
+	{
+		return it->second;
+	}
+	auto val = create();
+	map.emplace(key, val);
+	return val;
 }
 
+// HoversFromLocals is an implementation of vk::dbg::Variables that is used to
+// provide a scope's 'hover' variables - those that appear when you place the
+// cursor over a variable in an IDE.
+// Unlike the top-level SIMD lane grouping of variables in Frame::locals,
+// Frame::hovers displays each variable as a value per SIMD lane.
+// Instead maintaining another collection of variables per scope,
+// HoversFromLocals dynamically builds the hover information from the locals.
+class HoversFromLocals : public vk::dbg::Variables
+{
+public:
+	HoversFromLocals(const std::shared_ptr<vk::dbg::Variables> &locals)
+	    : locals(locals)
+	{}
+
+	void foreach(size_t startIndex, size_t count, const ForeachCallback &cb) override
+	{
+		// No op - hovers are only searched, never iterated.
+	}
+
+	std::shared_ptr<vk::dbg::Value> get(const std::string &name) override
+	{
+		// Is the hover variable a SIMD-common variable? If so, just return
+		// that.
+		if(auto val = locals->get(name))
+		{
+			return val;
+		}
+
+		// Search each of the lanes for the named variable.
+		// Collect them all up, and return that in a new Struct value.
+		bool found = false;
+		auto str = vk::dbg::Struct::create("", [&](auto &vc) {
+			for(int lane = 0; lane < sw::SIMD::Width; lane++)
+			{
+				auto laneN = laneName(lane);
+				if(auto laneV = locals->get(laneN))
+				{
+					if(auto children = laneV->children())
+					{
+						if(auto val = children->get(name))
+						{
+							vc->put(laneN, val);
+							found = true;
+						}
+					}
+				}
+			}
+		});
+
+		if(found)
+		{
+			// The value returned will be returned to the debug client by
+			// identifier. As the value is a Struct, the server will include
+			// a handle to the Variables, which needs to be kept alive so the
+			// client can send a request for its members.
+			// lastFind keeps any nested Variables alive long enough for them to
+			// be requested.
+			lastFind = str;
+			return str;
+		}
+
+		return nullptr;
+	}
+
+private:
+	std::shared_ptr<vk::dbg::Variables> locals;
+	std::shared_ptr<vk::dbg::Struct> lastFind;
+};
+
 }  // anonymous namespace
 
 namespace spvtools {
@@ -80,44 +295,19 @@
 
 }  // 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(char *s)
-{
-	return s;
-}
-std::string tostring(const char *s)
-{
-	return s;
-}
-template<typename T>
-std::string tostring(T *s)
-{
-	char buf[32];
-	snprintf(buf, sizeof(buf), "%p", s);
-	return buf;
-}
-std::string tostring(sw::SpirvShader::Object::ID id)
-{
-	return "%" + tostring(id.value());
-}
-
 ////////////////////////////////////////////////////////////////////////////////
+// namespace ::(anonymous)::debug
 // OpenCL.Debug.100 data structures
 ////////////////////////////////////////////////////////////////////////////////
+namespace {
 namespace debug {
 
+struct Declare;
+struct LocalVariable;
 struct Member;
+struct Value;
 
+// Object is the common base class for all the OpenCL.Debug.100 data structures.
 struct Object
 {
 	enum class Kind
@@ -195,22 +385,27 @@
 	return "<unknown>";
 }
 
-template<typename TYPE_, typename BASE, Object::Kind KIND_>
+// ObjectImpl is a helper template struct which simplifies deriving from Object.
+// ObjectImpl passes down the KIND to the Object constructor, and implements
+// kindof().
+template<typename TYPE, typename BASE, Object::Kind KIND>
 struct ObjectImpl : public BASE
 {
-	using ID = sw::SpirvID<TYPE_>;
-	static constexpr auto KIND = KIND_;
+	using ID = sw::SpirvID<TYPE>;
+	static constexpr auto Kind = KIND;
 
 	ObjectImpl()
-	    : BASE(KIND)
+	    : BASE(Kind)
 	{}
 	static_assert(BASE::kindof(KIND), "BASE::kindof() returned false");
 
 	// kindof() returns true iff kind is of this type, or any type deriving from
 	// this type.
-	static constexpr bool kindof(Object::Kind kind) { return kind == KIND; }
+	static constexpr bool kindof(Object::Kind kind) { return kind == Kind; }
 };
 
+// cast() casts the debug type pointer obj to TO.
+// If obj is null or not of the type TO, then nullptr is returned.
 template<typename TO, typename FROM>
 TO *cast(FROM *obj)
 {
@@ -218,6 +413,8 @@
 	return (TO::kindof(obj->kind)) ? static_cast<TO *>(obj) : nullptr;
 }
 
+// cast() casts the debug type pointer obj to TO.
+// If obj is null or not of the type TO, then nullptr is returned.
 template<typename TO, typename FROM>
 const TO *cast(const FROM *obj)
 {
@@ -225,11 +422,9 @@
 	return (TO::kindof(obj->kind)) ? static_cast<const TO *>(obj) : nullptr;
 }
 
+// Scope is the base class for all OpenCL.DebugInfo.100 scope objects.
 struct Scope : public Object
 {
-	// Global represents the global scope.
-	static const Scope Global;
-
 	using ID = sw::SpirvID<Scope>;
 	inline Scope(Kind kind)
 	    : Object(kind)
@@ -248,9 +443,17 @@
 	Scope *parent = nullptr;
 };
 
+// Type is the base class for all OpenCL.DebugInfo.100 type objects.
 struct Type : public Object
 {
 	using ID = sw::SpirvID<Type>;
+
+	struct Member
+	{
+		Type *type;
+		std::string name;
+	};
+
 	inline Type(Kind kind)
 	    : Object(kind)
 	{}
@@ -267,14 +470,8 @@
 		       kind == Kind::TemplateType;
 	}
 
-	std::pair<const Type *, uint32_t> index(std::queue<uint32_t> &&indices) const
-	{
-		if(indices.size() == 0)
-		{
-			return { this, 0 };
-		}
-		return indexMember(std::move(indices));
-	}
+	// name() returns the type name.
+	virtual std::string name() const = 0;
 
 	// sizeInBytes() returns the number of bytes of the given debug type.
 	virtual uint32_t sizeInBytes() const = 0;
@@ -283,16 +480,38 @@
 	// at ptr of this type.
 	virtual std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const = 0;
 
-protected:
-	// indexMember() returns the nested inner element debug type and byte offset
-	// from the base of this type, using the list of indices.
-	virtual std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&) const = 0;
+	// numMembers() returns the number of members for the given type.
+	virtual size_t numMembers() const = 0;
+
+	// getMember() returns the member by index.
+	virtual Member getMember(size_t) const = 0;
+
+	// undefined() returns a shared pointer to a vk::dbg::Value that represents
+	// an undefined value of this type.
+	std::shared_ptr<vk::dbg::Value> undefined() const
+	{
+		struct Undef : public vk::dbg::Value
+		{
+			Undef(const std::string &ty)
+			    : ty(ty)
+			{}
+			const std::string ty;
+			std::string type() override { return ty; }
+			std::string get(const vk::dbg::FormatFlags &) override { return "<undefined>"; }
+		};
+		return std::make_shared<Undef>(name());
+	}
 };
 
+// CompilationUnit represents the OpenCL.DebugInfo.100 DebugCompilationUnit
+// instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugCompilationUnit
 struct CompilationUnit : ObjectImpl<CompilationUnit, Scope, Object::Kind::CompilationUnit>
 {
 };
 
+// Source represents the OpenCL.DebugInfo.100 DebugSource instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugSource
 struct Source : ObjectImpl<Source, Object, Object::Kind::Source>
 {
 	spv::SourceLanguage language;
@@ -303,19 +522,18 @@
 	std::shared_ptr<vk::dbg::File> dbgFile;
 };
 
+// BasicType represents the OpenCL.DebugInfo.100 DebugBasicType instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugBasicType
 struct BasicType : ObjectImpl<BasicType, Type, Object::Kind::BasicType>
 {
-	std::string name;
+	std::string name_;
 	uint32_t size = 0;  // in bits.
 	OpenCLDebugInfo100DebugBaseTypeAttributeEncoding encoding = OpenCLDebugInfo100Unspecified;
 
+	std::string name() const override { return name_; }
 	uint32_t sizeInBytes() const override { return size / 8; }
-
-	std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&) const override
-	{
-		DABORT("indexMember() called on BasicType %s", name.c_str());
-		return {};
-	}
+	size_t numMembers() const override { return 0; }
+	Member getMember(size_t) const override { return {}; }
 
 	std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
 	{
@@ -344,183 +562,96 @@
 	}
 };
 
+// ArrayType represents the OpenCL.DebugInfo.100 DebugTypeArray instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugTypeArray
+//
+// Unlike OpenCL.DebugInfo.100's DebugTypeArray, ArrayType is always
+// single-dimensional. Multi-dimensional arrays are transformed into multiple
+// nested ArrayTypes. This is done to simplify logic.
 struct ArrayType : ObjectImpl<ArrayType, Type, Object::Kind::ArrayType>
 {
 	Type *base = nullptr;
-	std::vector<uint32_t> dimensions;
+	bool ownsBase = false;  // If true, base is owned by this ArrayType.
+	uint32_t size;          // In elements
 
-	// build() loops over each element of the multi-dimensional array, calling
-	// enter() for building each new dimension group, and element() for each
-	// inner-most dimension element.
-	//
-	// enter must be a function of the signature:
-	//   std::shared_ptr<vk::dbg::VariableContainer>
-	//       (std::shared_ptr<vk::dbg::VariableContainer>& parent, uint32_t idx)
-	// where:
-	//   parent is the outer dimension group
-	//   idx is the index of the next deepest dimension.
-	//
-	// element must be a function of the signature:
-	//   void(std::shared_ptr<vk::dbg::VariableContainer> &parent,
-	//        uint32_t idx, uint32_t offset)
-	// where:
-	//   parent is the penultimate deepest dimension group
-	//   idx is the index of the element in parent group
-	//   offset is the 'flattened array' index for the element.
-	template<typename GROUP, typename ENTER_FUNC, typename ELEMENT_FUNC>
-	void build(const GROUP &group, ENTER_FUNC &&enter, ELEMENT_FUNC &&element) const
+	~ArrayType()
 	{
-		if(dimensions.size() == 0) { return; }
-
-		struct Dimension
-		{
-			uint32_t idx = 0;
-			GROUP group;
-			bool built = false;
-		};
-
-		std::vector<Dimension> dims;
-		dims.resize(dimensions.size());
-
-		uint32_t offset = 0;
-
-		int dimIdx = 0;
-		const int n = static_cast<int>(dimensions.size()) - 1;
-		while(dimIdx >= 0)
-		{
-			// (Re)build groups to inner dimensions.
-			for(; dimIdx <= n; dimIdx++)
-			{
-				if(!dims[dimIdx].built)
-				{
-					dims[dimIdx].group = (dimIdx == 0)
-					                         ? group
-					                         : enter(dims[dimIdx - 1].group, dims[dimIdx - 1].idx);
-					dims[dimIdx].built = true;
-				}
-			}
-
-			// Emit each of the inner-most dimension elements.
-			for(dims[n].idx = 0; dims[n].idx < dimensions[n]; dims[n].idx++)
-			{
-				ASSERT(dims[n].built);
-				element(dims[n].group, dims[n].idx, offset++);
-			}
-
-			dimIdx = n;
-			while(dims[dimIdx].idx == dimensions[dimIdx])
-			{
-				dims[dimIdx] = {};  // Clear the the current dimension
-				dimIdx--;           // Step up a dimension
-				if(dimIdx < 0) { break; }
-				dims[dimIdx].idx++;  // Increment the next dimension index
-			}
-		}
+		if(ownsBase) { delete base; }
 	}
 
-	uint32_t sizeInBytes() const override
-	{
-		auto numBytes = base->sizeInBytes();
-		for(auto dim : dimensions)
-		{
-			numBytes *= dim;
-		}
-		return numBytes;
-	}
-
-	std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&indices) const override
-	{
-		std::vector<uint32_t> arrIndices(dimensions.size());
-		for(size_t i = 0; i < dimensions.size(); i++)
-		{
-			arrIndices[i] = take(indices);
-		}
-
-		auto out = base->index(std::move(indices));
-		auto stride = base->sizeInBytes();
-		for(int i = static_cast<int>(dimensions.size()) - 1; i >= 0; i--)
-		{
-			out.second += arrIndices[i] * stride;
-			stride *= dimensions[i];
-		}
-		return out;
-	}
+	std::string name() const override { return base->name() + "[]"; }
+	uint32_t sizeInBytes() const override { return base->sizeInBytes() * size; }
+	size_t numMembers() const override { return size; }
+	Member getMember(size_t i) const override { return { base, std::to_string(i) }; }
 
 	std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
 	{
-		auto vc = std::make_shared<vk::dbg::VariableContainer>();
-		build(
-		    vc,
-		    [&](std::shared_ptr<vk::dbg::VariableContainer> &parent, uint32_t idx) {
-			    auto child = std::make_shared<vk::dbg::VariableContainer>();
-			    parent->put(tostring(idx), std::make_shared<vk::dbg::Struct>("array", child));
-			    return child;
-		    },
-		    [&](std::shared_ptr<vk::dbg::VariableContainer> &parent, uint32_t idx, uint32_t offset) {
-			    offset = offset * base->sizeInBytes() * (interleaved ? sw::SIMD::Width : 1);
-			    auto addr = static_cast<uint8_t *>(ptr) + offset;
-			    auto child = base->value(addr, interleaved);
-			    auto key = tostring(idx);
+		auto members = std::make_shared<vk::dbg::VariableContainer>();
+
+		auto addr = static_cast<uint8_t *>(ptr);
+		for(size_t i = 0; i < size; i++)
+		{
+			auto member = getMember(i);
+
 #	if DEBUG_ANNOTATE_VARIABLE_KEYS
-			    key += " (" + tostring(addr) + " +" + tostring(offset) + ", idx: " + tostring(idx) + ")" + (interleaved ? "I" : "F");
+			key += " (" + std::to_string(addr) + " +" + std::to_string(offset) + ", i: " + std::to_string(i) + ")" + (interleaved ? "I" : "F");
 #	endif
-			    parent->put(key, child);
-		    });
-		return std::make_shared<vk::dbg::Struct>("array", vc);
+			members->put(member.name, base->value(addr, interleaved));
+
+			addr += base->sizeInBytes() * (interleaved ? sw::SIMD::Width : 1);
+		}
+		return std::make_shared<vk::dbg::Struct>(name(), members);
 	}
 };
 
+// VectorType represents the OpenCL.DebugInfo.100 DebugTypeVector instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugTypeVector
 struct VectorType : ObjectImpl<VectorType, Type, Object::Kind::VectorType>
 {
 	Type *base = nullptr;
 	uint32_t components = 0;
 
-	uint32_t sizeInBytes() const override
-	{
-		return base->sizeInBytes() * components;
-	}
-
-	std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&indices) const override
-	{
-		auto idx = take(indices);
-		auto out = base->index(std::move(indices));
-		out.second += base->sizeInBytes() * idx;
-		return out;
-	}
+	std::string name() const override { return "vec" + std::to_string(components) + "<" + base->name() + ">"; }
+	uint32_t sizeInBytes() const override { return base->sizeInBytes() * components; }
+	size_t numMembers() const override { return components; }
+	Member getMember(size_t i) const override { return { base, vecElementName(i, components) }; }
 
 	std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
 	{
 		const auto elSize = base->sizeInBytes();
-		auto vc = std::make_shared<vk::dbg::VariableContainer>();
+		auto members = std::make_shared<vk::dbg::VariableContainer>();
 		for(uint32_t i = 0; i < components; i++)
 		{
 			auto offset = elSize * i * (interleaved ? sw::SIMD::Width : 1);
 			auto elPtr = static_cast<uint8_t *>(ptr) + offset;
-			auto elKey = (components > 4) ? tostring(i) : &"x\0y\0z\0w\0"[i * 2];
 #	if DEBUG_ANNOTATE_VARIABLE_KEYS
-			elKey += " (" + tostring(elPtr) + " +" + tostring(offset) + ")" + (interleaved ? "I" : "F");
+			elKey += " (" + std::to_string(elPtr) + " +" + std::to_string(offset) + ")" + (interleaved ? "I" : "F");
 #	endif
-			vc->put(elKey, base->value(elPtr, interleaved));
+			members->put(getMember(i).name, base->value(elPtr, interleaved));
 		}
-		return std::make_shared<vk::dbg::Struct>("vector", vc);
+		return std::make_shared<vk::dbg::Struct>(name(), members);
 	}
 };
 
+// FunctionType represents the OpenCL.DebugInfo.100 DebugTypeFunction
+// instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugTypeFunction
 struct FunctionType : ObjectImpl<FunctionType, Type, Object::Kind::FunctionType>
 {
 	uint32_t flags = 0;  // OR'd from OpenCLDebugInfo100DebugInfoFlags
 	Type *returnTy = nullptr;
 	std::vector<Type *> paramTys;
 
+	std::string name() const override { return "function"; }
 	uint32_t sizeInBytes() const override { return 0; }
-	std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&indices) const override
-	{
-		DABORT("indexMember() called on FunctionType");
-		return {};
-	}
+	size_t numMembers() const override { return 0; }
+	Member getMember(size_t i) const override { return {}; }
 	std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override { return nullptr; }
 };
 
+// Member represents the OpenCL.DebugInfo.100 DebugTypeMember instruction.
+// Despite the instruction name, this is not a type - rather a member of a type.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugTypeMember
 struct Member : ObjectImpl<Member, Object, Object::Kind::Member>
 {
 	std::string name;
@@ -534,9 +665,12 @@
 	uint32_t flags = 0;   // OR'd from OpenCLDebugInfo100DebugInfoFlags
 };
 
+// CompositeType represents the OpenCL.DebugInfo.100 DebugTypeComposite
+// instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugTypeComposite
 struct CompositeType : ObjectImpl<CompositeType, Type, Object::Kind::CompositeType>
 {
-	std::string name;
+	std::string name_;
 	OpenCLDebugInfo100DebugCompositeType tag = OpenCLDebugInfo100Class;
 	Source *source = nullptr;
 	uint32_t line = 0;
@@ -545,36 +679,33 @@
 	std::string linkage;
 	uint32_t size = 0;   // in bits.
 	uint32_t flags = 0;  // OR'd from OpenCLDebugInfo100DebugInfoFlags
-	std::vector<Member *> members;
+	std::vector<debug::Member *> members_;
 
+	std::string name() const override { return name_; }
 	uint32_t sizeInBytes() const override { return size / 8; }
-
-	std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&indices) const override
-	{
-		auto idx = take(indices);
-		auto member = members[idx];
-		auto out = member->type->index(std::move(indices));
-		out.second += member->offset / 8;
-		return out;
-	}
+	size_t numMembers() const override { return members_.size(); }
+	Member getMember(size_t i) const override { return { members_[i]->type, members_[i]->name }; }
 
 	std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
 	{
-		auto vc = std::make_shared<vk::dbg::VariableContainer>();
-		for(auto &member : members)
+		auto fields = std::make_shared<vk::dbg::VariableContainer>();
+		for(auto &member : members_)
 		{
 			auto offset = (member->offset / 8) * (interleaved ? sw::SIMD::Width : 1);
 			auto elPtr = static_cast<uint8_t *>(ptr) + offset;
 			auto elKey = member->name;
 #	if DEBUG_ANNOTATE_VARIABLE_KEYS
-			// elKey += " (" + tostring(elPtr) + " +" + tostring(offset) + ")" + (interleaved ? "I" : "F");
+			// elKey += " (" + std::to_string(elPtr) + " +" + std::to_string(offset) + ")" + (interleaved ? "I" : "F");
 #	endif
-			vc->put(elKey, member->type->value(elPtr, interleaved));
+			fields->put(elKey, member->type->value(elPtr, interleaved));
 		}
-		return std::make_shared<vk::dbg::Struct>(name, vc);
+		return std::make_shared<vk::dbg::Struct>(name_, fields);
 	}
 };
 
+// TemplateParameter represents the OpenCL.DebugInfo.100
+// DebugTypeTemplateParameter instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugTypeTemplateParameter
 struct TemplateParameter : ObjectImpl<TemplateParameter, Object, Object::Kind::TemplateParameter>
 {
 	std::string name;
@@ -585,22 +716,26 @@
 	uint32_t column = 0;
 };
 
+// TemplateType represents the OpenCL.DebugInfo.100 DebugTypeTemplate
+// instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugTypeTemplate
 struct TemplateType : ObjectImpl<TemplateType, Type, Object::Kind::TemplateType>
 {
 	Type *target = nullptr;  // Class, struct or function.
 	std::vector<TemplateParameter *> parameters;
 
+	std::string name() const override { return "template<>"; }
 	uint32_t sizeInBytes() const override { return target->sizeInBytes(); }
-	std::pair<const Type *, uint32_t> indexMember(std::queue<uint32_t> &&indices) const override
-	{
-		return target->index(std::move(indices));
-	}
+	size_t numMembers() const override { return 0; }
+	Member getMember(size_t i) const override { return {}; }
 	std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
 	{
 		return target->value(ptr, interleaved);
 	}
 };
 
+// Function represents the OpenCL.DebugInfo.100 DebugFunction instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugFunction
 struct Function : ObjectImpl<Function, Scope, Object::Kind::Function>
 {
 	std::string name;
@@ -613,13 +748,19 @@
 	sw::SpirvShader::Function::ID function;
 };
 
+// LexicalBlock represents the OpenCL.DebugInfo.100 DebugLexicalBlock instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugLexicalBlock
 struct LexicalBlock : ObjectImpl<LexicalBlock, Scope, Object::Kind::LexicalBlock>
 {
 	uint32_t line = 0;
 	uint32_t column = 0;
 	std::string name;
+
+	std::vector<LocalVariable *> variables;
 };
 
+// InlinedAt represents the OpenCL.DebugInfo.100 DebugInlinedAt instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugInlinedAt
 struct InlinedAt : ObjectImpl<InlinedAt, Object, Object::Kind::InlinedAt>
 {
 	uint32_t line = 0;
@@ -627,12 +768,16 @@
 	InlinedAt *inlined = nullptr;
 };
 
+// SourceScope represents the OpenCL.DebugInfo.100 DebugScope instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugScope
 struct SourceScope : ObjectImpl<SourceScope, Object, Object::Kind::SourceScope>
 {
 	Scope *scope = nullptr;
 	InlinedAt *inlinedAt = nullptr;
 };
 
+// GlobalVariable represents the OpenCL.DebugInfo.100 DebugGlobalVariable instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugGlobalVariable
 struct GlobalVariable : ObjectImpl<GlobalVariable, Object, Object::Kind::GlobalVariable>
 {
 	std::string name;
@@ -646,10 +791,24 @@
 	uint32_t flags = 0;  // OR'd from OpenCLDebugInfo100DebugInfoFlags
 };
 
+// LocalVariable represents the OpenCL.DebugInfo.100 DebugLocalVariable
+// instruction.
+// Local variables are essentially just a scoped variable name.
+// Their value comes from either a DebugDeclare (which has an immutable pointer
+// to the actual data), or from a number of DebugValues (which can change
+// any nested members of the variable over time).
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugLocalVariable
 struct LocalVariable : ObjectImpl<LocalVariable, Object, Object::Kind::LocalVariable>
 {
 	static constexpr uint32_t NoArg = ~uint32_t(0);
 
+	enum class Definition
+	{
+		Undefined,    // Variable has no defined value
+		Declaration,  // Variable value comes from definition
+		Values        // Variable value comes from values
+	};
+
 	std::string name;
 	Type *type = nullptr;
 	Source *source = nullptr;
@@ -657,19 +816,40 @@
 	uint32_t column = 0;
 	Scope *parent = nullptr;
 	uint32_t arg = NoArg;
+
+	Definition definition = Definition::Undefined;
+	Declare *declaration = nullptr;  // Used if definition == Definition::Declaration
+
+	// ValueNode is a tree node of debug::Value definitions.
+	// Each node in the tree represents an element in the type tree.
+	struct ValueNode
+	{
+		// NoDebugValueIndex indicates that this node is never assigned a value.
+		static constexpr const uint32_t NoDebugValueIndex = ~0u;
+
+		uint32_t debugValueIndex = NoDebugValueIndex;  // Index into State::lastReachedDebugValues
+		std::unordered_map<uint32_t, std::unique_ptr<ValueNode>> children;
+	};
+	ValueNode values;  // Used if definition == Definition::Values
 };
 
+// Operation represents the OpenCL.DebugInfo.100 DebugOperation instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugOperation
 struct Operation : ObjectImpl<Operation, Object, Object::Kind::Operation>
 {
 	uint32_t opcode = 0;
 	std::vector<uint32_t> operands;
 };
 
+// Expression represents the OpenCL.DebugInfo.100 DebugExpression instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugExpression
 struct Expression : ObjectImpl<Expression, Object, Object::Kind::Expression>
 {
 	std::vector<Operation *> operations;
 };
 
+// Declare represents the OpenCL.DebugInfo.100 DebugDeclare instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugDeclare
 struct Declare : ObjectImpl<Declare, Object, Object::Kind::Declare>
 {
 	LocalVariable *local = nullptr;
@@ -677,6 +857,8 @@
 	Expression *expression = nullptr;
 };
 
+// Value represents the OpenCL.DebugInfo.100 DebugValue instruction.
+// https://www.khronos.org/registry/spir-v/specs/unified1/OpenCL.DebugInfo.100.html#DebugValue
 struct Value : ObjectImpl<Value, Object, Object::Kind::Value>
 {
 	LocalVariable *local = nullptr;
@@ -685,8 +867,6 @@
 	std::vector<uint32_t> indexes;
 };
 
-const Scope Scope::Global = CompilationUnit{};
-
 // find<T>() searches the nested scopes, returning for the first scope that is
 // castable to type T. If no scope can be found of type T, then nullptr is
 // returned.
@@ -697,108 +877,172 @@
 	return scope->parent ? find<T>(scope->parent) : nullptr;
 }
 
-bool hasDebuggerScope(debug::Scope *spirvScope)
-{
-	return debug::cast<debug::Function>(spirvScope) != nullptr ||
-	       debug::cast<debug::LexicalBlock>(spirvScope) != nullptr;
-}
-
 }  // namespace debug
 }  // anonymous namespace
 
-namespace rr {
-
 ////////////////////////////////////////////////////////////////////////////////
-// rr::CToReactor<T> specializations.
+// namespace ::sw
+//
+// Implementations for:
+//   sw::SpirvShader::Impl::Debugger
+//   sw::SpirvShader::Impl::Debugger::LocalVariableValue
+//   sw::SpirvShader::Impl::Debugger::State
+//   sw::SpirvShader::Impl::Debugger::State::Data
 ////////////////////////////////////////////////////////////////////////////////
-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.
+// SpirvShader-private struct holding compile-time-mutable and
+// execution-time-immutable debugger information.
+//
 // There is an instance of this class per shader program.
 ////////////////////////////////////////////////////////////////////////////////
-struct SpirvShader::Impl::Debugger
+struct SpirvShader::Impl::Debugger : public vk::dbg::ClientEventListener
 {
-	class Group;
 	class State;
+	class LocalVariableValue;
+
+	Debugger(const SpirvShader *shader, const std::shared_ptr<vk::dbg::Context> &ctx);
+	~Debugger();
 
 	enum class Pass
 	{
-		Define,
-		Emit
+		Define,  // Pre-pass (called from SpirvShader constructor)
+		Emit     // Code generation pass (called from SpirvShader::emit()).
 	};
 
-	void process(const SpirvShader *shader, const InsnIterator &insn, EmitState *state, Pass pass);
+	// process() is called for each debugger instruction in two compiler passes.
+	// For the Define pass, process() constructs ::debug objects and
+	// registers them in the objects map.
+	// For the Emit pass, process() populates the fields of ::debug objects and
+	// potentially emits instructions for the shader program.
+	void process(const InsnIterator &insn, EmitState *state, Pass pass);
 
-	void setNextSetLocationIsStep();
-	void setLocation(EmitState *state, const std::shared_ptr<vk::dbg::File> &, int line, int column);
-	void setLocation(EmitState *state, const std::string &path, int line, int column);
+	// finalize() must be called after all shader instruction have been emitted.
+	// finalize() allocates the trap memory and registers the Debugger for
+	// client debugger events so that it can monitor for changes in breakpoints.
+	void finalize();
 
-	// foreachLane() calls f for each debugger group representing the SIMD
-	// lanes of execution.
-	// FUNC is a function with the signature:
-	//   (int lane, const Group &group, auto &key)
-	template<typename Key, typename Func>
-	void foreachLane(const Key &key, const debug::Scope *scope, EmitState *state, Func &&f) const;
+	// setNextSetLocationIsSteppable() indicates that the next call to
+	// setLocation() must be a debugger steppable line.
+	void setNextSetLocationIsSteppable();
 
-	// 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,
-	    const debug::Scope *scope,
-	    const debug::Type *type,
-	    Object::ID id,
-	    EmitState *state) const;
+	// setScope() sets the current debug source scope. Used by setLocation()
+	// when the next location is debugger steppable.
+	void setScope(debug::SourceScope *);
 
-	// 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,
-	    const debug::Type *type,
-	    Object::ID id,
-	    EmitState *state,
-	    int wordOffset = 0) const;
+	// setLocation() sets the current codegen source location to the given file
+	// and line.
+	void setLocation(EmitState *state, const std::shared_ptr<vk::dbg::File> &, int line);
+	void setLocation(EmitState *state, const char *file, int line);
 
-	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
+	using SpirvInstruction = const void *;
 
-	// Shadow memory is used to construct a contiguous memory block for local
-	// variables that may be formed from multiple SSA values.
+	const SpirvShader *const shader;                              // The shader program being debugged
+	std::shared_ptr<vk::dbg::Context> const ctx;                  // The debugger context
+	bool shaderHasDebugInfo;                                      // True if the shader has high-level debug info (OpenCL.Debug100 instructions)
+	std::shared_ptr<vk::dbg::File> spirvFile;                     // Virtual file containing SPIR-V disassembly instructions
+	std::unordered_map<SpirvInstruction, int> spirvLineMappings;  // Instruction pointer to line
+	std::unordered_map<SpirvInstruction, Object::ID> results;     // Instruction pointer to result ID
+
+	// LocationAndScope holds a source location and scope pair.
+	struct LocationAndScope
+	{
+		vk::dbg::Location location;
+		debug::SourceScope *scope;
+
+		inline bool operator==(const LocationAndScope &other) const
+		{
+			return location == other.location && scope == other.scope;
+		}
+		struct Hash
+		{
+			uint64_t operator()(const LocationAndScope &l) const
+			{
+				return std::hash<decltype(l.location)>()(l.location) ^ std::hash<decltype(l.scope)>()(l.scope);
+			}
+		};
+	};
+
+	// Traps holds information about debugger traps - points in the shader
+	// program where execution may pause for the debugger, either due to hitting
+	// a breakpoint or following a single line step.
+	// The Traps::memory is continually read during execution of a shader,
+	// triggering a trap when the byte is non-zero. Traps can also be enabled
+	// via the State::alwaysTrap field.
+	struct Traps
+	{
+		// Source location + scope -> line trap index
+		std::unordered_map<LocationAndScope, size_t, LocationAndScope::Hash> byLocationAndScope;
+
+		// Function name -> entry trap index
+		std::unordered_map<std::string, size_t> byFunctionName;
+
+		// Trap index -> source location + scope
+		std::vector<LocationAndScope> byIndex;
+
+		// Trap memory - shared for all running instances of the shader.
+		// Each byte represents a single trap enabled (1) / disabled (0) state.
+		std::unique_ptr<uint8_t[]> memory;
+	} traps;
+
+	// Shadow memory is used to construct a contiguous memory block
+	// (State::shadow) that contains an up-to-date copy of each
+	// SpirvShader::Object's value(s) in the currently executing shader.
+	// Shadow memory either contains SIMD-interleaved values for all components
+	// in the object, or a SIMD-pointer (Shadow::Pointer).
 	struct Shadow
 	{
-		// Offset in the shadow memory allocation for the given local variable.
-		std::unordered_map<debug::LocalVariable *, uint32_t> offsets;
+		// Entry describes the byte offset and kind of the shadow memory for
+		// a single SpirvShader::Object.
+		struct Entry
+		{
+			enum class Kind
+			{
+				Value,
+				Pointer,
+			};
+			Kind kind;
+			uint32_t offset;
+		};
 
-		// Total size of the shadow memory in bytes.
-		uint32_t size;
+		// Pointer is the structure stored in shadow memory for pointer types.
+		// The address for a given SIMD lane is the base + offsets[lane].
+		struct Pointer
+		{
+			uint8_t *base;                      // Common base address for all SIMD lanes.
+			uint32_t offsets[sw::SIMD::Width];  // Per lane offsets.
+		};
+
+		// Memory is returned by get().
+		// Memory holds a pointer (addr) to the entry in the shadow memory, and
+		// provides the dref() method for dereferencing a pointer for the given
+		// SIMD lane.
+		struct Memory
+		{
+			inline operator void *();
+			inline Memory dref(int lane) const;
+			uint8_t *addr;
+		};
+
+		// create() adds a new entry for the object with the given id.
+		void create(const SpirvShader *, const EmitState *, Object::ID);
+
+		// get() returns a Memory pointing to the shadow memory for the object
+		// with the given id for the given SIMD lane.
+		Memory get(const State *, Object::ID, int lane) const;
+
+		std::unordered_map<Object::ID, Entry> entries;
+		uint32_t size = 0;  // Total size of the shadow memory in bytes.
 	} shadow;
 
+	// vk::dbg::ClientEventListener
+	void onSetBreakpoint(const vk::dbg::Location &location, bool &handled) override;
+	void onSetBreakpoint(const std::string &func, bool &handled) override;
+	void onBreakpointsChanged() override;
+
 private:
 	// add() registers the debug object with the given id.
 	template<typename ID>
@@ -840,405 +1084,431 @@
 	void defineOrEmit(InsnIterator insn, Pass pass, F &&emit);
 
 	std::unordered_map<std::string, std::shared_ptr<vk::dbg::File>> files;
-	bool nextSetLocationIsStep = true;
-	int lastSetLocationLine = 0;
+	uint32_t numDebugValueSlots = 0;  // Number of independent debug::Values which need to be tracked
+	bool nextSetLocationIsSteppable = true;
+	debug::SourceScope *lastSetScope = nullptr;
+	vk::dbg::Location lastSetLocation;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// sw::SpirvShader::Impl::Debugger::LocalVariableValue
+//
+// Implementation of vk::dbg::Value that displays a debug::LocalVariable that
+// has its value(s) defined by debug::Value(s).
+//
+// TODO(b/145351270) Note: The OpenCL.DebugInfo.100 spec does not state how
+// DebugValues should be applied to the DebugLocalVariable.
+//
+// This implementation keeps track of the order of DebugValues as they are
+// 'executed', and uses the most recent values for each specific index.
+// OpenCL.DebugInfo.100 is significantly derived from the LLVM debug
+// instructions, and so it can be assumed that DebugValue is intended to behave
+// like llvm.dbg.value.
+//
+// https://llvm.org/docs/SourceLevelDebugging.html#object-lifetime-in-optimized-code
+// describes the expected behavior of llvm.dbg.value, which instead of runtime
+// tracking, uses static analysis of the LLVM IR to determine which debug
+// values should be used.
+//
+// If DebugValue is to behave the same way as llvm.dbg.value, then this
+// implementation should be changed to examine the order of DebugValue
+// instructions in the SPIR-V. This can only be done once the SPIR-V generating
+// compiler and SPIR-V optimization passes generate and preserve the DebugValue
+// ordering as described in the LLVM SourceLevelDebugging document.
+////////////////////////////////////////////////////////////////////////////////
+class sw::SpirvShader::Impl::Debugger::LocalVariableValue : public vk::dbg::Value
+{
+public:
+	// Data shared across all nodes in the LocalVariableValue.
+	struct Shared
+	{
+		Shared(debug::LocalVariable const *const variable, State const *const state, int const lane)
+		    : variable(variable)
+		    , state(state)
+		    , lane(lane)
+		{
+			ASSERT(variable->definition == debug::LocalVariable::Definition::Values);
+		}
+
+		debug::LocalVariable const *const variable;
+		State const *const state;
+		int const lane;
+	};
+
+	LocalVariableValue(debug::LocalVariable *variable, State const *const state, int lane);
+
+	LocalVariableValue(
+	    std::shared_ptr<const Shared> const &shared,
+	    debug::Type const *ty,
+	    debug::LocalVariable::ValueNode const *node);
+
+private:
+	// vk::dbg::Value
+	std::string type() override;
+	std::string get(const vk::dbg::FormatFlags &) override;
+	std::shared_ptr<vk::dbg::Variables> children() override;
+
+	void updateValue();
+	std::shared_ptr<const Shared> const shared;
+	debug::Type const *const ty;
+	debug::LocalVariable::ValueNode const *const node;
+	debug::Value *activeValue = nullptr;
+	std::shared_ptr<vk::dbg::Value> value;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 // sw::SpirvShader::Impl::Debugger::State
 //
 // State holds the runtime data structures for the shader debug session.
+//
+// When debugging is enabled, the shader program will construct a State with a
+// call to create(), and during execution write shader information into fields
+// of this class, including:
+//  * Shadow memory for keeping track of register-held values.
+//  * Global variables.
+//  * Last reached ::debug::Values (see LocalVariableValue)
+//
+// Bulky data that is only needed once the shader has hit a trap is held by
+// State::Data. This is lazily constructed by the first call to trap().
+//
 // There is an instance of this class per shader invocation.
 ////////////////////////////////////////////////////////////////////////////////
 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);
-	~State();
-
-	void enter(const char *name);
-	void exit();
-	void updateActiveLaneMask(int lane, bool enabled);
-	void updateLocation(bool isStep, vk::dbg::File::ID file, int line, int column);
-	void createScope(const debug::Scope *);
-	void setScope(debug::SourceScope *newScope);
-
-	vk::dbg::VariableContainer *hovers(const debug::Scope *);
-	vk::dbg::VariableContainer *localsLane(const debug::Scope *, 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);
-
-	template<typename K>
-	void putPtr(vk::dbg::VariableContainer *vc, K key, void *ptr, bool interleaved, const debug::Type *type);
-
-	template<typename K, typename V>
-	void putRef(vk::dbg::VariableContainer *vc, K key, V *ptr);
-
-	// Scopes holds pointers to the vk::dbg::Scopes for local variables, hover
-	// variables and the locals indexed by SIMD lane.
-	struct Scopes
+	// Globals holds a copy of the shader's builtin global variables.
+	struct Globals
 	{
-		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;
+		struct Compute
+		{
+			sw::uint3 numWorkgroups;
+			sw::uint3 workgroupID;
+			sw::uint3 workgroupSize;
+			uint32_t numSubgroups;
+			uint32_t subgroupIndex;
+			sw::SIMD::uint3 globalInvocationId;
+			sw::SIMD::uint3 localInvocationId;
+			sw::SIMD::uint3 localInvocationIndex;
+		};
+		struct Fragment
+		{
+			uint32_t viewIndex;
+			sw::SIMD::vec4 fragCoord;
+			sw::SIMD::vec4 pointCoord;
+			sw::SIMD::int2 windowSpacePosition;
+			sw::SIMD::uint_t helperInvocation;
+		};
+		struct Vertex
+		{
+			uint32_t viewIndex;
+			uint32_t instanceIndex;
+			sw::SIMD::uint_t vertexIndex;
+		};
+
+		// Common for all shader types
+		uint32_t subgroupSize;
+		sw::SIMD::uint_t activeLaneMask;
+
+		// Shader type specific globals
+		union
+		{
+			Compute compute;
+			Fragment fragment;
+			Vertex vertex;
+		};
 	};
 
-	// getScopes() returns the Scopes object for the given debug::Scope.
-	const Scopes &getScopes(const debug::Scope *scope);
+	// create() allocates, constructs and returns a State.
+	// Called at the start of the debugger-enabled shader program.
+	static State *create(const Debugger *debugger);
 
-	const Debugger *debugger;
-	const std::shared_ptr<vk::dbg::Thread> thread;
+	// destroy() destructs and frees a state.
+	// Called at the end of the debugger-enabled shader program.
+	static void destroy(State *);
+
+	// trap() is called by the debugger-enabled shader program to suspend
+	// execution of the shader. This will appear in the attached debugger as if
+	// a breakpoint has been hit.
+	// trap() will be called if the Debugger::Traps::memory[index] is non-zero,
+	// or if alwaysTrap is non-zero.
+	// index is the index of the trap (see Debugger::Traps).
+	void trap(int index);
+
+	const Debugger *const debugger;
+
+	// traps is a simple copy of Debugger::Traps::memory.
+	// Copied here to reduce pointer chasing during shader execution.
+	uint8_t *traps = nullptr;
+
+	// alwaysTrap (if non-zero) forces a call trap() even if
+	// Debugger::Traps::memory[index] is zero. Used to perform single line
+	// stepping (pause at next line / instruction).
+	uint8_t alwaysTrap = 0;
+
+	// Global variable values. Written to at shader start.
+	Globals globals;
+
+	// Shadow memory for all SpirvShader::Objects in the executing shader
+	// program.
+	// See Debugger::Shadow for more information.
 	std::unique_ptr<uint8_t[]> const shadow;
-	std::unordered_map<const debug::Scope *, Scopes> scopes;
-	Scopes globals;                          // Scope for globals.
-	debug::SourceScope *srcScope = nullptr;  // Current source scope.
 
-	const size_t initialThreadDepth = 0;
-};
-
-SpirvShader::Impl::Debugger::State *SpirvShader::Impl::Debugger::State::create(const Debugger *debugger, const char *name)
-{
-	return new State(debugger, name);
-}
-
-void SpirvShader::Impl::Debugger::State::destroy(State *state)
-{
-	delete state;
-}
-
-SpirvShader::Impl::Debugger::State::State(const Debugger *debugger, const char *stackBase)
-    : debugger(debugger)
-    , thread(debugger->ctx->lock().currentThread())
-    , shadow(new uint8_t[debugger->shadow.size])
-    , initialThreadDepth(thread->depth())
-{
-	enter(stackBase);
-
-	thread->update(true, [&](vk::dbg::Frame &frame) {
-		globals.locals = frame.locals;
-		globals.hovers = frame.hovers;
-		for(int i = 0; i < sw::SIMD::Width; i++)
-		{
-			auto locals = std::make_shared<vk::dbg::VariableContainer>();
-			frame.locals->variables->put(laneNames[i], std::make_shared<vk::dbg::Struct>("", locals));
-			globals.localsByLane[i] = locals;
-		}
-	});
-}
-
-SpirvShader::Impl::Debugger::State::~State()
-{
-	for(auto depth = thread->depth(); depth > initialThreadDepth; depth--)
-	{
-		exit();
-	}
-}
-
-void SpirvShader::Impl::Debugger::State::enter(const char *name)
-{
-	thread->enter(debugger->spirvFile, name);
-}
-
-void SpirvShader::Impl::Debugger::State::exit()
-{
-	thread->exit();
-}
-
-void SpirvShader::Impl::Debugger::State::updateActiveLaneMask(int lane, bool enabled)
-{
-	globals.localsByLane[lane]->put("enabled", vk::dbg::make_constant(enabled));
-}
-
-void SpirvShader::Impl::Debugger::State::updateLocation(bool isStep, vk::dbg::File::ID fileID, int line, int column)
-{
-	auto file = debugger->ctx->lock().get(fileID);
-	thread->update(isStep, [&](vk::dbg::Frame &frame) {
-		frame.location = { file, line, column };
-	});
-}
-
-vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::hovers(const debug::Scope *scope)
-{
-	return getScopes(scope).hovers->variables.get();
-}
-
-vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::localsLane(const debug::Scope *scope, int i)
-{
-	return getScopes(scope).localsByLane[i].get();
-}
-
-template<typename K>
-vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::group(vk::dbg::VariableContainer *vc, K key)
-{
-	auto out = std::make_shared<vk::dbg::VariableContainer>();
-	vc->put(tostring(key), std::make_shared<vk::dbg::Struct>("", 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));
-}
-
-template<typename K>
-void SpirvShader::Impl::Debugger::State::putPtr(vk::dbg::VariableContainer *vc, K key, void *ptr, bool interleaved, const debug::Type *type)
-{
-	vc->put(tostring(key), type->value(ptr, interleaved));
-}
-
-template<typename K, typename V>
-void SpirvShader::Impl::Debugger::State::putRef(vk::dbg::VariableContainer *vc, K key, V *ptr)
-{
-	vc->put(tostring(key), vk::dbg::make_reference(*ptr));
-}
-
-void SpirvShader::Impl::Debugger::State::createScope(const debug::Scope *spirvScope)
-{
-	// TODO(b/151338669): We're creating scopes per-shader invocation.
-	// This is all really static information, and should only be created
-	// once *per program*.
-
-	ASSERT(spirvScope != nullptr);
-
-	auto lock = debugger->ctx->lock();
-	Scopes s = {};
-	s.locals = lock.createScope(spirvScope->source->dbgFile);
-	s.hovers = lock.createScope(spirvScope->source->dbgFile);
-
-	for(int i = 0; i < sw::SIMD::Width; i++)
-	{
-		auto locals = std::make_shared<vk::dbg::VariableContainer>();
-		s.localsByLane[i] = locals;
-		s.locals->variables->put(laneNames[i], std::make_shared<vk::dbg::Struct>("", locals));
-	}
-
-	if(hasDebuggerScope(spirvScope->parent))
-	{
-		auto parent = getScopes(spirvScope->parent);
-		for(int i = 0; i < sw::SIMD::Width; i++)
-		{
-			s.localsByLane[i]->extend(parent.localsByLane[i]);
-		}
-		s.hovers->variables->extend(parent.hovers->variables);
-	}
-	else
-	{
-		// Scope has no parent. Ensure the globals are inherited for this stack
-		// frame.
-		//
-		// Note: We're combining globals with locals as DAP doesn't have a
-		// 'globals' enum value for Scope::presentationHint.
-		// TODO(bclayton): We should probably keep globals separate from locals
-		// and combine them at the server interface. That way we can easily
-		// provide globals if DAP later supports it as a Scope::presentationHint
-		// type.
-		for(int i = 0; i < sw::SIMD::Width; i++)
-		{
-			s.localsByLane[i]->extend(globals.localsByLane[i]);
-		}
-	}
-
-	scopes.emplace(spirvScope, std::move(s));
-}
-
-void SpirvShader::Impl::Debugger::State::setScope(debug::SourceScope *newSrcScope)
-{
-	auto oldSrcScope = srcScope;
-	if(oldSrcScope == newSrcScope) { return; }
-	srcScope = newSrcScope;
-
-	if(hasDebuggerScope(srcScope->scope))
-	{
-		auto thread = debugger->ctx->lock().currentThread();
-
-		debug::Function *oldFunction = oldSrcScope ? debug::find<debug::Function>(oldSrcScope->scope) : nullptr;
-		debug::Function *newFunction = newSrcScope ? debug::find<debug::Function>(newSrcScope->scope) : nullptr;
-
-		if(oldFunction != newFunction)
-		{
-			if(oldFunction) { thread->exit(); }
-			if(newFunction) { thread->enter(newFunction->source->dbgFile, newFunction->name); }
-		}
-
-		auto dbgScope = getScopes(srcScope->scope);
-		thread->update(true, [&](vk::dbg::Frame &frame) {
-			frame.locals = dbgScope.locals;
-			frame.hovers = dbgScope.hovers;
-		});
-	}
-}
-
-const SpirvShader::Impl::Debugger::State::Scopes &SpirvShader::Impl::Debugger::State::getScopes(const debug::Scope *scope)
-{
-	if(scope == &debug::Scope::Global)
-	{
-		return globals;
-	}
-
-	auto dbgScopeIt = scopes.find(scope);
-	ASSERT_MSG(dbgScopeIt != scopes.end(),
-	           "createScope() not called for debug::Scope %s %p",
-	           cstr(scope->kind), scope);
-	return dbgScopeIt->second;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// 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, const debug::Scope *scope);
-	static Group locals(Ptr state, const debug::Scope *scope);
-	static Group localsLane(Ptr state, const debug::Scope *scope, int lane);
-	static Group globals(Ptr state, int lane);
-
-	inline Group();
-	inline Group(Ptr state, Ptr group);
-
-	inline bool isValid() const;
-
-	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 RK>
-	void putPtr(RK key, RValue<Pointer<Byte>> ptr, bool interleaved, const debug::Type *type) const;
-
-	template<typename K, typename V, typename RK>
-	void putRef(RK key, RValue<Pointer<Byte>> ref) 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;
+	// Array of last reached debug::Value.
+	// Indexed by ::debug::LocalVariable::ValueNode::debugValueIndex.
+	std::unique_ptr<debug::Value *[]> const lastReachedDebugValues;
 
 private:
-	Ptr state;
-	Ptr ptr;
-	bool valid = false;
+	// Data holds the debugger-interface state (vk::dbg::*).
+	// This is only constructed on the first call to Debugger::State::trap() as
+	// it contains data that is only needed when the debugger is actively
+	// inspecting execution of the shader program.
+	struct Data
+	{
+		Data(State *state);
+
+		// terminate() is called at the end of execution of the shader program.
+		// terminate() ensures that the debugger thread stack is at the same
+		// level as when the program entered.
+		void terminate(State *state);
+
+		// trap() updates the debugger thread with the stack frames and
+		// variables at the trap's scoped location.
+		// trap() will notify the debugger that the thread has paused, and will
+		// block until instructed to resume (either continue or step) by the
+		// user.
+		void trap(int index, State *state);
+
+	private:
+		using PerLaneVariables = std::array<std::shared_ptr<vk::dbg::VariableContainer>, sw::SIMD::Width>;
+
+		struct StackEntry
+		{
+			debug::LexicalBlock *block;
+			uint32_t line;
+
+			bool operator!=(const StackEntry &other) const { return block != other.block || line != other.line; }
+		};
+
+		struct GlobalVariables
+		{
+			std::shared_ptr<vk::dbg::VariableContainer> common;
+			PerLaneVariables lanes;
+		};
+
+		// updateFrameLocals() updates the local variables in the frame with
+		// those in the lexical block.
+		void updateFrameLocals(State *state, vk::dbg::Frame &frame, debug::LexicalBlock *block);
+
+		// getOrCreateLocals() creates and returns the per-lane local variables
+		// from those in the lexical block.
+		PerLaneVariables getOrCreateLocals(State *state, debug::LexicalBlock const *block);
+
+		// buildGlobal() creates and adds to globals global variable with the
+		// given name and value. The value is copied instead of holding a
+		// pointer to val.
+		template<typename T>
+		void buildGlobal(const char *name, const T &val);
+		template<typename T, int N>
+		void buildGlobal(const char *name, const sw::SIMD::PerLane<T, N> &vec);
+
+		// buildGlobals() builds all the global variable values, populating
+		// globals.
+		void buildGlobals(State *state);
+
+		// buildSpirvVariables() builds a Struct holding all the SPIR-V named
+		// values for the given lane.
+		std::shared_ptr<vk::dbg::Struct> buildSpirvVariables(State *state, int lane) const;
+
+		// buildSpirvValue() returns a debugger value for the SPIR-V shadow
+		// value at memory of the given type and for the given lane.
+		std::shared_ptr<vk::dbg::Value> buildSpirvValue(State *state, Shadow::Memory memory, const SpirvShader::Type &type, int lane) const;
+
+		GlobalVariables globals;
+		std::shared_ptr<vk::dbg::Thread> thread;
+		std::vector<StackEntry> stack;
+		std::unordered_map<debug::LexicalBlock const *, PerLaneVariables> locals;
+	};
+
+	State(const Debugger *debugger);
+	~State();
+	std::unique_ptr<Data> data;
 };
 
-SpirvShader::Impl::Debugger::Group
-SpirvShader::Impl::Debugger::Group::hovers(Ptr state, const debug::Scope *scope)
-{
-	return Group(state, rr::Call(&State::hovers, state, scope));
-}
-
-SpirvShader::Impl::Debugger::Group
-SpirvShader::Impl::Debugger::Group::localsLane(Ptr state, const debug::Scope *scope, int lane)
-{
-	return Group(state, rr::Call(&State::localsLane, state, scope, lane));
-}
-
-SpirvShader::Impl::Debugger::Group
-SpirvShader::Impl::Debugger::Group::globals(Ptr state, int lane)
-{
-	return localsLane(state, &debug::Scope::Global, lane);
-}
-
-SpirvShader::Impl::Debugger::Group::Group() {}
-
-SpirvShader::Impl::Debugger::Group::Group(Ptr state, Ptr group)
-    : state(state)
-    , ptr(group)
-    , valid(true)
-{}
-
-bool SpirvShader::Impl::Debugger::Group::isValid() const
-{
-	return valid;
-}
-
-template<typename K, typename RK>
-SpirvShader::Impl::Debugger::Group SpirvShader::Impl::Debugger::Group::group(RK key) const
-{
-	ASSERT(isValid());
-	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
-{
-	ASSERT(isValid());
-	rr::Call(&State::putVal<K, V>, state, ptr, key, value);
-}
-
-template<typename K, typename RK>
-void SpirvShader::Impl::Debugger::Group::putPtr(RK key, RValue<Pointer<Byte>> addr, bool interleaved, const debug::Type *type) const
-{
-	ASSERT(isValid());
-	rr::Call(&State::putPtr<K>, state, ptr, key, addr, interleaved, type);
-}
-
-template<typename K, typename V, typename RK>
-void SpirvShader::Impl::Debugger::Group::putRef(RK key, RValue<Pointer<Byte>> ref) const
-{
-	ASSERT(isValid());
-	rr::Call(&State::putRef<K, V>, state, ptr, key, ref);
-}
-
-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
 ////////////////////////////////////////////////////////////////////////////////
+SpirvShader::Impl::Debugger::Debugger(const SpirvShader *shader, const std::shared_ptr<vk::dbg::Context> &ctx)
+    : shader(shader)
+    , ctx(ctx)
+{
+}
+
+SpirvShader::Impl::Debugger::~Debugger()
+{
+	ctx->removeListener(this);
+}
+
+void SpirvShader::Impl::Debugger::finalize()
+{
+	ASSERT(traps.byIndex.size() == traps.byLocationAndScope.size());
+	traps.memory = std::make_unique<uint8_t[]>(traps.byIndex.size());
+
+	ctx->addListener(this);
+
+	// Register existing breakpoints.
+	onBreakpointsChanged();
+}
+
+void sw::SpirvShader::Impl::Debugger::setNextSetLocationIsSteppable()
+{
+	nextSetLocationIsSteppable = true;
+}
+
+void SpirvShader::Impl::Debugger::setScope(debug::SourceScope *scope)
+{
+	lastSetScope = scope;
+}
+
+void SpirvShader::Impl::Debugger::setLocation(EmitState *state, const std::shared_ptr<vk::dbg::File> &file, int line)
+{
+	vk::dbg::Location location{ file, line };
+
+	if(location != lastSetLocation)
+	{
+		// If the location has changed, then this is always a step.
+		nextSetLocationIsSteppable = true;
+		lastSetLocation = location;
+	}
+
+	if(nextSetLocationIsSteppable)
+	{
+		// Get or create the trap for the given location and scope.
+		LocationAndScope locationAndScope{ location, lastSetScope };
+		int index = getOrCreate(traps.byLocationAndScope, locationAndScope, [&] {
+			traps.byIndex.emplace_back(locationAndScope);
+			return traps.byIndex.size() - 1;
+		});
+
+		// Also create a map index for the given scope's function so we can
+		// break on function entry.
+		if(lastSetScope)
+		{
+			if(auto func = debug::find<debug::Function>(lastSetScope->scope))
+			{
+				getOrCreate(traps.byFunctionName, func->name, [&] { return index; });
+			}
+		}
+
+		// Emit the shader logic to test the trap value (either through via
+		// Debugger::State::traps[] or Debugger::State::alwaysTrap), and call
+		// Debugger::State::trap() if either are true.
+		auto dbgState = state->routine->dbgState;
+		auto alwaysTrap = *Pointer<Byte>(dbgState + OFFSET(Impl::Debugger::State, alwaysTrap));
+		auto traps = *Pointer<Pointer<Byte>>(dbgState + OFFSET(Impl::Debugger::State, traps));
+		auto trap = Pointer<Byte>(traps)[index];
+		If(alwaysTrap != Byte(0) || trap != Byte(0))
+		{
+			rr::Call(&State::trap, state->routine->dbgState, index);
+		}
+		nextSetLocationIsSteppable = false;
+	}
+}
+
+void SpirvShader::Impl::Debugger::setLocation(EmitState *state, const char *path, int line)
+{
+	auto lock = ctx->lock();
+	auto file = lock.findFile(path);
+	if(!file)
+	{
+		file = lock.createPhysicalFile(path);
+	}
+	setLocation(state, file, line);
+}
+
+void SpirvShader::Impl::Debugger::onSetBreakpoint(const vk::dbg::Location &location, bool &handled)
+{
+	// Notify the debugger if the breakpoint location is handled.
+	// We don't actually set the trap here as this is performed by
+	// onBreakpointsChanged(), which is only called once, even for multiple
+	// breakpoint changes.
+	for(auto it : traps.byLocationAndScope)
+	{
+		if(location == it.first.location)
+		{
+			handled = true;
+			return;
+		}
+	}
+}
+
+void SpirvShader::Impl::Debugger::onSetBreakpoint(const std::string &func, bool &handled)
+{
+	// Notify the debugger if the function-entry breakpoint is handled.
+	// We don't actually set the trap here as this is performed by
+	// onBreakpointsChanged(), which is only called once, even for multiple
+	// breakpoint changes.
+	auto it = traps.byFunctionName.find(func);
+	if(it != traps.byFunctionName.end())
+	{
+		handled = true;
+	}
+
+	if(isEntryBreakpointForShaderType(shader->executionModel, func))
+	{
+		handled = true;
+	}
+}
+
+void SpirvShader::Impl::Debugger::onBreakpointsChanged()
+{
+	// TODO(b/145351270): TSAN will probably moan that traps.memory is being
+	// modified while being read on othe threads. We can solve this by adding
+	// a shared mutex (RWMutex) for the traps, read-locking for execution, and
+	// write locking here. This will prevent setting breakpoints while a shader
+	// is executing (maybe problematic if you want to debug a slow or
+	// never-completing shader).
+	// For now, just be racy. It's unlikely that this will cause any noticable
+	// problems.
+
+	// Start by disabling all traps.
+	memset(traps.memory.get(), 0, traps.byIndex.size() * sizeof(traps.memory[0]));
+
+	// Add traps for all breakpoints by location.
+	for(auto it : files)
+	{
+		auto &file = it.second;
+		for(auto line : file->getBreakpoints())
+		{
+			for(auto it : traps.byLocationAndScope)
+			{
+				if(it.first.location == vk::dbg::Location{ file, line })
+				{
+					traps.memory[it.second] = 1;
+				}
+			}
+		}
+	}
+
+	// Add traps for all breakpoints by function name.
+	auto lock = ctx->lock();
+	for(auto it : traps.byFunctionName)
+	{
+		if(lock.isFunctionBreakpoint(it.first))
+		{
+			traps.memory[it.second] = 1;
+		}
+	}
+
+	// Add traps for breakpoints by shader type.
+	for(auto bp : lock.getFunctionBreakpoints())
+	{
+		if(isEntryBreakpointForShaderType(shader->executionModel, bp))
+		{
+			traps.memory[0] = 1;
+		}
+	}
+}
+
 template<typename F, typename T>
 void SpirvShader::Impl::Debugger::defineOrEmit(InsnIterator insn, Pass pass, F &&emit)
 {
@@ -1254,9 +1524,8 @@
 	}
 }
 
-void SpirvShader::Impl::Debugger::process(const SpirvShader *shader, const InsnIterator &insn, EmitState *state, Pass pass)
+void SpirvShader::Impl::Debugger::process(const InsnIterator &insn, EmitState *state, Pass pass)
 {
-	auto dbg = shader->impl.debugger;
 	auto extInstIndex = insn.word(4);
 	switch(extInstIndex)
 	{
@@ -1273,7 +1542,7 @@
 			break;
 		case OpenCLDebugInfo100DebugTypeBasic:
 			defineOrEmit(insn, pass, [&](debug::BasicType *type) {
-				type->name = shader->getString(insn.word(5));
+				type->name_ = shader->getString(insn.word(5));
 				type->size = shader->GetConstScalarInt(insn.word(6));
 				type->encoding = static_cast<OpenCLDebugInfo100DebugBaseTypeAttributeEncoding>(insn.word(7));
 			});
@@ -1281,9 +1550,17 @@
 		case OpenCLDebugInfo100DebugTypeArray:
 			defineOrEmit(insn, pass, [&](debug::ArrayType *type) {
 				type->base = get(debug::Type::ID(insn.word(5)));
-				for(uint32_t i = 6; i < insn.wordCount(); i++)
+				type->size = shader->GetConstScalarInt(insn.word(6));
+				for(uint32_t i = 7; i < insn.wordCount(); i++)
 				{
-					type->dimensions.emplace_back(shader->GetConstScalarInt(insn.word(i)));
+					// Decompose multi-dimentional into nested single
+					// dimensional arrays. Greatly simplifies logic.
+					auto inner = new debug::ArrayType();
+					inner->base = type->base;
+					type->size = shader->GetConstScalarInt(insn.word(i));
+					type->base = inner;
+					type->ownsBase = true;
+					type = inner;
 				}
 			});
 			break;
@@ -1310,7 +1587,7 @@
 			break;
 		case OpenCLDebugInfo100DebugTypeComposite:
 			defineOrEmit(insn, pass, [&](debug::CompositeType *type) {
-				type->name = shader->getString(insn.word(5));
+				type->name_ = shader->getString(insn.word(5));
 				type->tag = static_cast<OpenCLDebugInfo100DebugCompositeType>(insn.word(6));
 				type->source = get(debug::Source::ID(insn.word(7)));
 				type->line = insn.word(8);
@@ -1324,7 +1601,7 @@
 					auto obj = get(debug::Object::ID(insn.word(i)));
 					if(auto member = debug::cast<debug::Member>(obj))  // Can also be Function or TypeInheritance, which we don't care about.
 					{
-						type->members.push_back(member);
+						type->members_.push_back(member);
 					}
 				}
 			});
@@ -1373,12 +1650,6 @@
 				var->variable = isNone(insn.word(12)) ? 0 : insn.word(12);
 				var->flags = insn.word(13);
 				// static member declaration: word(14)
-
-				// TODO(b/148401179): Instead of simply hiding variables that have been stripped by optimizations, show them in the debugger as `<optimized-away>`
-				if(var->variable != 0)
-				{
-					exposeVariable(shader, var->name.c_str(), &debug::Scope::Global, var->type, var->variable, state);
-				}
 			});
 			break;
 		case OpenCLDebugInfo100DebugFunction:
@@ -1394,8 +1665,6 @@
 				func->scopeLine = insn.word(13);
 				func->function = Function::ID(insn.word(14));
 				// declaration: word(13)
-
-				rr::Call(&State::createScope, state->routine->dbgState, func);
 			});
 			break;
 		case OpenCLDebugInfo100DebugLexicalBlock:
@@ -1408,8 +1677,6 @@
 				{
 					scope->name = shader->getString(insn.word(9));
 				}
-
-				rr::Call(&State::createScope, state->routine->dbgState, scope);
 			});
 			break;
 		case OpenCLDebugInfo100DebugScope:
@@ -1419,8 +1686,7 @@
 				{
 					ss->inlinedAt = get(debug::InlinedAt::ID(insn.word(6)));
 				}
-
-				rr::Call(&State::setScope, state->routine->dbgState, ss);
+				setScope(ss);
 			});
 			break;
 		case OpenCLDebugInfo100DebugNoScope:
@@ -1447,6 +1713,10 @@
 				{
 					var->arg = insn.word(11);
 				}
+				if(auto block = debug::find<debug::LexicalBlock>(var->parent))
+				{
+					block->variables.emplace_back(var);
+				}
 			});
 			break;
 		case OpenCLDebugInfo100DebugDeclare:
@@ -1454,105 +1724,54 @@
 				decl->local = get(debug::LocalVariable::ID(insn.word(5)));
 				decl->variable = Object::ID(insn.word(6));
 				decl->expression = get(debug::Expression::ID(insn.word(7)));
-				exposeVariable(
-				    shader,
-				    decl->local->name.c_str(),
-				    decl->local->parent,
-				    decl->local->type,
-				    decl->variable,
-				    state);
+
+				decl->local->declaration = decl;
+
+				ASSERT(decl->local->definition == debug::LocalVariable::Definition::Undefined);
+				decl->local->definition = debug::LocalVariable::Definition::Declaration;
 			});
 			break;
 		case OpenCLDebugInfo100DebugValue:
 			defineOrEmit(insn, pass, [&](debug::Value *value) {
 				value->local = get(debug::LocalVariable::ID(insn.word(5)));
-				value->value = Object::ID(insn.word(6));
+				value->value = insn.word(6);
 				value->expression = get(debug::Expression::ID(insn.word(7)));
+
+				if(value->local->definition == debug::LocalVariable::Definition::Undefined)
+				{
+					value->local->definition = debug::LocalVariable::Definition::Values;
+				}
+				ASSERT(value->local->definition == debug::LocalVariable::Definition::Values);
+
+				auto node = &value->local->values;
 				for(uint32_t i = 8; i < insn.wordCount(); i++)
 				{
 					auto idx = shader->GetConstScalarInt(insn.word(i));
 					value->indexes.push_back(idx);
+
+					auto it = node->children.find(i);
+					if(it != node->children.end())
+					{
+						node = it->second.get();
+					}
+					else
+					{
+						auto parent = node;
+						auto child = std::make_unique<debug::LocalVariable::ValueNode>();
+						node = child.get();
+						parent->children.emplace(i, std::move(child));
+					}
 				}
 
-				// DebugValue partially updates a DebugLocalVariable with an SSA
-				// value. This is typically used to update a DebugLocalVariable
-				// of a composite type, which holds structure member offsets
-				// from a base address.
-				// To handle these, we allocate shadow memory to hold a copy of
-				// the entire variable in contiguous memory and have the
-				// DebugLocalVariable point to this memory. Whenever we
-				// encounter a DebugValue, we copy the necessary fields to the
-				// shadow memory.
-
-				// type of the full DebugLocalVariable.
-				auto type = value->local->type;
-
-				// base address of the variable.
-				// Start by pointing base to the root of the shadow memory.
-				// This will be offset to the variable, then the member within
-				// the variable below.
-				SIMD::Pointer base(*Pointer<Pointer<Byte>>(state->routine->dbgState + OFFSET(State, shadow)), shadow.size);
-
-				// All variables are considered local, and therefore
-				// interleaved.
-				base = InterleaveByLane(base);
-
-				// Have we already allocated shadow memory for this variable?
-				auto it = shadow.offsets.find(value->local);
-				if(it == shadow.offsets.end())
+				if(node->debugValueIndex == debug::LocalVariable::ValueNode::NoDebugValueIndex)
 				{
-					// No shadow memory has been allocated for this local
-					// variable yet.
-
-					// Allocate the memory for the variable.
-					auto offset = shadow.size;
-					shadow.offsets.emplace(value->local, offset);
-					auto size = type->sizeInBytes() * SIMD::Width;
-					base += offset;
-					shadow.size += size;
-
-					// Expose the variable.
-					auto name = value->local->name.c_str();
-					auto scope = value->local->parent;
-					auto offsets = base.offsets();
-					foreachLane(name, scope, state, [&](int lane, const Group &group, auto &key) {
-						auto ptr = base.base + Extract(offsets, lane);
-						group.putPtr<const char *>(name, ptr, true, value->local->type);
-					});
-				}
-				else
-				{
-					// Shadow memory already allocated for this variable.
-					// Offset base to point to it.
-					base += it->second;
+					node->debugValueIndex = numDebugValueSlots++;
 				}
 
-				// Find the byte offset on the indexed member of the variable.
-				std::queue<uint32_t> indices;
-				for(auto idx : value->indexes)
-				{
-					indices.emplace(idx);
-				}
-				auto offset = type->index(std::move(indices)).second;
-
-				// Update base to point to the particular member.
-				base += offset;
-
-				// Now copy the updated value into shadow memory representation
-				// of the variable.
-				// TODO(b/148401179): This assumes tight packing of all
-				// components, which may not match with the debug structure
-				// layout.
-				auto &valObject = shader->getObject(value->value);
-				auto &valType = shader->getType(valObject);
-				for(auto i = 0u; i < valType.componentCount; i++)
-				{
-					auto val = Operand(shader, state, value->value).Int(i);
-					auto dst = base + i * sizeof(uint32_t) * SIMD::Width;
-					// Use RobustBufferAccess as the size as described by the
-					// debug type may be smaller than the true SSA size.
-					dst.Store(val, sw::OutOfBoundsBehavior::RobustBufferAccess, state->activeLaneMask());
-				}
+				rr::Pointer<rr::Pointer<Byte>> lastReachedArray = *rr::Pointer<rr::Pointer<rr::Pointer<Byte>>>(
+				    state->routine->dbgState + OFFSET(Impl::Debugger::State, lastReachedDebugValues));
+				rr::Pointer<rr::Pointer<Byte>> lastReached = &lastReachedArray[node->debugValueIndex];
+				*lastReached = rr::ConstantPointer(value);
 			});
 			break;
 		case OpenCLDebugInfo100DebugExpression:
@@ -1569,13 +1788,13 @@
 				if(insn.wordCount() > 6)
 				{
 					source->source = shader->getString(insn.word(6));
-					auto file = dbg->ctx->lock().createVirtualFile(source->file.c_str(), source->source.c_str());
+					auto file = ctx->lock().createVirtualFile(source->file.c_str(), source->source.c_str());
 					source->dbgFile = file;
 					files.emplace(source->file.c_str(), file);
 				}
 				else
 				{
-					auto file = dbg->ctx->lock().createPhysicalFile(source->file.c_str());
+					auto file = ctx->lock().createPhysicalFile(source->file.c_str());
 					source->dbgFile = file;
 					files.emplace(source->file.c_str(), file);
 				}
@@ -1612,32 +1831,6 @@
 	}
 }
 
-void SpirvShader::Impl::Debugger::setNextSetLocationIsStep()
-{
-	nextSetLocationIsStep = true;
-}
-
-void SpirvShader::Impl::Debugger::setLocation(EmitState *state, const std::shared_ptr<vk::dbg::File> &file, int line, int column)
-{
-	if(line != lastSetLocationLine)
-	{
-		// If the line number has changed, then this is always a step.
-		nextSetLocationIsStep = true;
-		lastSetLocationLine = line;
-	}
-	rr::Call(&State::updateLocation, state->routine->dbgState, nextSetLocationIsStep, file->id, line, column);
-	nextSetLocationIsStep = false;
-}
-
-void SpirvShader::Impl::Debugger::setLocation(EmitState *state, const std::string &path, int line, int column)
-{
-	auto it = files.find(path);
-	if(it != files.end())
-	{
-		setLocation(state, it->second, line, column);
-	}
-}
-
 template<typename ID>
 void SpirvShader::Impl::Debugger::add(ID id, std::unique_ptr<debug::Object> &&obj)
 {
@@ -1681,242 +1874,594 @@
 	return ptr;
 }
 
-template<typename Key, typename Func>
-void SpirvShader::Impl::Debugger::foreachLane(
-    const Key &key,
-    const debug::Scope *scope,
-    EmitState *state,
-    Func &&f) const
+////////////////////////////////////////////////////////////////////////////////
+// SpirvShader::Impl::Debugger::Shadow methods
+////////////////////////////////////////////////////////////////////////////////
+void SpirvShader::Impl::Debugger::Shadow::create(const SpirvShader *shader, const EmitState *state, Object::ID objId)
 {
-	auto dbgState = state->routine->dbgState;
-	auto hover = Group::hovers(dbgState, scope).group<Key>(key);
-	for(int lane = 0; lane < SIMD::Width; lane++)
+	ASSERT_MSG(entries.find(objId) == entries.end(),
+	           "Object %%%d already has shadow memory allocated?", (int)objId.value());
+
+	Entry entry{};
+	entry.offset = size;
+
+	rr::Pointer<Byte> base = *rr::Pointer<rr::Pointer<Byte>>(state->routine->dbgState + OFFSET(Impl::Debugger::State, shadow));
+	base += entry.offset;
+
+	auto &obj = shader->getObject(objId);
+	auto &objTy = shader->getType(obj.typeId());
+	auto mask = state->activeLaneMask();
+	switch(obj.kind)
 	{
-		f(lane, Group::localsLane(dbgState, scope, lane), key);
-		f(lane, hover, laneNames[lane]);
+		case Object::Kind::Constant:
+		case Object::Kind::Intermediate:
+		{
+			size += objTy.componentCount * sizeof(uint32_t) * sw::SIMD::Width;
+			auto dst = InterleaveByLane(SIMD::Pointer(base, 0));
+			for(uint32_t i = 0u; i < objTy.componentCount; i++)
+			{
+				auto val = SpirvShader::Operand(shader, state, objId).Int(i);
+				dst.Store(val, sw::OutOfBoundsBehavior::UndefinedBehavior, mask);
+				dst += sizeof(uint32_t) * SIMD::Width;
+			}
+			entry.kind = Entry::Kind::Value;
+			break;
+		}
+		case Object::Kind::Pointer:
+		case Object::Kind::InterfaceVariable:
+		{
+			size += sizeof(void *) + sizeof(uint32_t) * SIMD::Width;
+			auto ptr = state->getPointer(objId);
+			store(base, ptr.base);
+			store(base + sizeof(void *), ptr.offsets());
+			entry.kind = Entry::Kind::Pointer;
+			break;
+		}
+		default:
+			break;
+	}
+	entries.emplace(objId, entry);
+}
+
+SpirvShader::Impl::Debugger::Shadow::Memory
+SpirvShader::Impl::Debugger::Shadow::get(const State *state, Object::ID objId, int lane) const
+{
+	auto entryIt = entries.find(objId);
+	ASSERT_MSG(entryIt != entries.end(), "Missing shadow entry for object %%%d (%s)",
+	           (int)objId.value(),
+	           OpcodeName(state->debugger->shader->getObject(objId).opcode()).c_str());
+	auto &entry = entryIt->second;
+	auto data = &state->shadow[entry.offset];
+	return Memory{ data };
+}
+
+SpirvShader::Impl::Debugger::Shadow::Memory::operator void *()
+{
+	return addr;
+}
+
+SpirvShader::Impl::Debugger::Shadow::Memory
+SpirvShader::Impl::Debugger::Shadow::Memory::dref(int lane) const
+{
+	auto ptr = *reinterpret_cast<Pointer *>(addr);
+	return Memory{ ptr.base + ptr.offsets[lane] };
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sw::SpirvShader::Impl::Debugger::LocalVariableValue methods
+////////////////////////////////////////////////////////////////////////////////
+sw::SpirvShader::Impl::Debugger::LocalVariableValue::LocalVariableValue(
+    debug::LocalVariable *variable,
+    State const *const state,
+    int lane)
+    : LocalVariableValue(std::make_shared<Shared>(variable, state, lane), variable->type, &variable->values)
+{}
+
+sw::SpirvShader::Impl::Debugger::LocalVariableValue::LocalVariableValue(
+    std::shared_ptr<const Shared> const &shared,
+    debug::Type const *ty,
+    debug::LocalVariable::ValueNode const *node)
+    : shared(shared)
+    , ty(ty)
+    , node(node)
+{
+}
+
+std::string sw::SpirvShader::Impl::Debugger::LocalVariableValue::type()
+{
+	updateValue();
+	return value->type();
+}
+
+std::string sw::SpirvShader::Impl::Debugger::LocalVariableValue::get(const vk::dbg::FormatFlags &fmt)
+{
+	updateValue();
+	return value->get(fmt);
+}
+
+std::shared_ptr<vk::dbg::Variables> sw::SpirvShader::Impl::Debugger::LocalVariableValue::children()
+{
+	updateValue();
+	return value->children();
+}
+
+void sw::SpirvShader::Impl::Debugger::LocalVariableValue::updateValue()
+{
+	// Fetch the last reached ::debug::Value for this local variable node.
+	auto newActiveValue = (node->debugValueIndex != debug::LocalVariable::ValueNode::NoDebugValueIndex)
+	                          ? shared->state->lastReachedDebugValues[node->debugValueIndex]
+	                          : nullptr;
+	auto activeValueChanged = activeValue != newActiveValue;
+	activeValue = newActiveValue;
+
+	if(activeValue && activeValueChanged)
+	{  // We have a new ::debug::Value, read it.
+
+		ASSERT(activeValue->local == shared->variable);  // If this isn't true, then something is very wonky.
+
+		// Update the value.
+		auto ptr = shared->state->debugger->shadow.get(shared->state, activeValue->value, shared->lane);
+		for(auto op : activeValue->expression->operations)
+		{
+			switch(op->opcode)
+			{
+				case OpenCLDebugInfo100Deref:
+					ptr = ptr.dref(shared->lane);
+					break;
+				default:
+					UNIMPLEMENTED("b/148401179 OpenCLDebugInfo100DebugOperation %d", (int)op->opcode);
+					break;
+			}
+		}
+		value = ty->value(ptr, true);
+	}
+	else if(!value || activeValueChanged)
+	{  // We have no ::debug::Value. Display <undefined>
+
+		if(node->children.empty())
+		{  // No children? Just have the node display <undefined>
+			value = ty->undefined();
+		}
+		else
+		{  // Node has children.
+			// Display <undefined> for those that don't have sub-nodes, and
+			// create child LocalVariableValues for those that do.
+			value = vk::dbg::Struct::create(ty->name(), [&](auto &vc) {
+				auto numMembers = ty->numMembers();
+				for(size_t i = 0; i < numMembers; i++)
+				{
+					auto member = ty->getMember(i);
+
+					auto it = node->children.find(i);
+					if(it != node->children.end())
+					{
+						auto child = std::make_shared<LocalVariableValue>(shared, member.type, it->second.get());
+						vc->put(member.name, child);
+					}
+					else
+					{
+						vc->put(member.name, member.type->undefined());
+					}
+				}
+			});
+		}
 	}
 }
 
-template<typename Key>
-void SpirvShader::Impl::Debugger::exposeVariable(
-    const SpirvShader *shader,
-    const Key &key,
-    const debug::Scope *scope,
-    const debug::Type *type,
-    Object::ID id,
-    EmitState *state) const
+////////////////////////////////////////////////////////////////////////////////
+// sw::SpirvShader::Impl::Debugger::State methods
+////////////////////////////////////////////////////////////////////////////////
+SpirvShader::Impl::Debugger::State *SpirvShader::Impl::Debugger::State::create(const Debugger *debugger)
 {
-	foreachLane(key, scope, state, [&](int lane, const Group &group, auto &key) {
-		exposeVariable(shader, group, lane, laneNames[lane], type, id, state);
-	});
+	return new State(debugger);
 }
 
-template<typename Key>
-void SpirvShader::Impl::Debugger::exposeVariable(
-    const SpirvShader *shader,
-    const Group &group,
-    int lane,
-    const Key &key,
-    const debug::Type *type,
-    Object::ID id,
-    EmitState *state,
-    int wordOffset /* = 0 */) const
+void SpirvShader::Impl::Debugger::State::destroy(State *state)
 {
-	auto &obj = shader->getObject(id);
+	delete state;
+}
 
-	if(type != nullptr)
+SpirvShader::Impl::Debugger::State::State(const Debugger *debugger)
+    : debugger(debugger)
+    , traps(debugger->traps.memory.get())
+    , shadow(new uint8_t[debugger->shadow.size])
+    , lastReachedDebugValues(new debug::Value *[debugger->numDebugValueSlots])
+{
+	memset(shadow.get(), 0, debugger->shadow.size);
+	memset(lastReachedDebugValues.get(), 0, sizeof(lastReachedDebugValues[0]) * debugger->numDebugValueSlots);
+}
+
+SpirvShader::Impl::Debugger::State::~State()
+{
+	if(data) { data->terminate(this); }
+}
+
+void SpirvShader::Impl::Debugger::State::trap(int index)
+{
+	if(std::all_of(globals.activeLaneMask.data.begin(),
+	               globals.activeLaneMask.data.end(),
+	               [](auto v) { return v == 0; }))
 	{
-		switch(obj.kind)
-		{
-			case Object::Kind::InterfaceVariable:
-			case Object::Kind::Pointer:
-			case Object::Kind::DescriptorSet:
-			{
-				ASSERT(wordOffset == 0);                            // TODO.
-				auto ptr = shader->GetPointerToData(id, 0, state);  //  + sizeof(uint32_t) * wordOffset;
-				auto &ptrTy = shader->getType(obj);
-				auto interleaved = IsStorageInterleavedByLane(ptrTy.storageClass);
-				if(interleaved)
-				{
-					ptr = InterleaveByLane(ptr);
-				}
-				auto addr = &ptr.base[Extract(ptr.offsets(), lane)];
-				group.putPtr<Key>(key, addr, interleaved, type);
-			}
-			break;
-
-			case Object::Kind::Constant:
-			case Object::Kind::Intermediate:
-			{
-				if(auto ty = debug::cast<debug::BasicType>(type))
-				{
-					auto val = Operand(shader, state, id).Int(wordOffset);
-					switch(ty->encoding)
-					{
-						case OpenCLDebugInfo100Address:
-							// TODO: This function takes a SIMD vector, and pointers cannot
-							// be held in them.
-							break;
-						case OpenCLDebugInfo100Boolean:
-							group.put<Key, bool>(key, Extract(val, lane) != 0);
-							break;
-						case OpenCLDebugInfo100Float:
-							group.put<Key, float>(key, Extract(As<SIMD::Float>(val), lane));
-							break;
-						case OpenCLDebugInfo100Signed:
-							group.put<Key, int>(key, Extract(val, lane));
-							break;
-						case OpenCLDebugInfo100SignedChar:
-							group.put<Key, int8_t>(key, SByte(Extract(val, lane)));
-							break;
-						case OpenCLDebugInfo100Unsigned:
-							group.put<Key, unsigned int>(key, Extract(val, lane));
-							break;
-						case OpenCLDebugInfo100UnsignedChar:
-							group.put<Key, uint8_t>(key, Byte(Extract(val, lane)));
-							break;
-						default:
-							break;
-					}
-				}
-				else if(auto ty = debug::cast<debug::VectorType>(type))
-				{
-					auto elWords = 1;  // Currently vector elements must only be basic types, 32-bit wide
-					auto elTy = ty->base;
-					auto vecGroup = group.group<Key>(key);
-					switch(ty->components)
-					{
-						case 1:
-							exposeVariable(shader, vecGroup, lane, "x", elTy, id, state, wordOffset + 0 * elWords);
-							break;
-						case 2:
-							exposeVariable(shader, vecGroup, lane, "x", elTy, id, state, wordOffset + 0 * elWords);
-							exposeVariable(shader, vecGroup, lane, "y", elTy, id, state, wordOffset + 1 * elWords);
-							break;
-						case 3:
-							exposeVariable(shader, vecGroup, lane, "x", elTy, id, state, wordOffset + 0 * elWords);
-							exposeVariable(shader, vecGroup, lane, "y", elTy, id, state, wordOffset + 1 * elWords);
-							exposeVariable(shader, vecGroup, lane, "z", elTy, id, state, wordOffset + 2 * elWords);
-							break;
-						case 4:
-							exposeVariable(shader, vecGroup, lane, "x", elTy, id, state, wordOffset + 0 * elWords);
-							exposeVariable(shader, vecGroup, lane, "y", elTy, id, state, wordOffset + 1 * elWords);
-							exposeVariable(shader, vecGroup, lane, "z", elTy, id, state, wordOffset + 2 * elWords);
-							exposeVariable(shader, vecGroup, lane, "w", elTy, id, state, wordOffset + 3 * elWords);
-							break;
-						default:
-							for(uint32_t i = 0; i < ty->components; i++)
-							{
-								exposeVariable(shader, vecGroup, lane, tostring(i).c_str(), elTy, id, state, wordOffset + i * elWords);
-							}
-							break;
-					}
-				}
-				else if(auto ty = debug::cast<debug::CompositeType>(type))
-				{
-					auto objectGroup = group.group<Key>(key);
-
-					for(auto member : ty->members)
-					{
-						exposeVariable(shader, objectGroup, lane, member->name.c_str(), member->type, id, state, member->offset / 32);
-					}
-				}
-				else if(auto ty = debug::cast<debug::ArrayType>(type))
-				{
-					ty->build(
-					    group.group<Key>(key),
-					    [&](Debugger::Group &parent, uint32_t idx) {
-						    return parent.template group<int>(idx);
-					    },
-					    [&](Debugger::Group &parent, uint32_t idx, uint32_t offset) {
-						    exposeVariable(shader, parent, lane, idx, ty->base, id, state, offset);
-					    });
-				}
-				else
-				{
-					UNIMPLEMENTED("b/145351270: Debug type: %s", cstr(type->kind));
-				}
-				return;
-			}
-			break;
-
-			case Object::Kind::Unknown:
-				UNIMPLEMENTED("b/145351270: Object kind: %d", (int)obj.kind);
-		}
+		// Don't trap if no lanes are active.
+		// Ideally, we would be simply jumping over blocks that have no active
+		// lanes, but this is complicated due to ensuring that all reactor
+		// RValues dominate their usage blocks.
 		return;
 	}
 
-	// No debug type information. Derive from SPIR-V.
-	switch(shader->getType(obj).opcode())
+	if(!data)
 	{
-		case spv::OpTypeInt:
-		{
-			Operand val(shader, state, id);
-			group.put<Key, int>(key, Extract(val.Int(0), lane));
-		}
-		break;
-		case spv::OpTypeFloat:
-		{
-			Operand val(shader, state, id);
-			group.put<Key, float>(key, Extract(val.Float(0), lane));
-		}
-		break;
-		case spv::OpTypeVector:
-		{
-			Operand val(shader, state, id);
-			auto count = shader->getType(obj).definition.word(3);
-			switch(count)
+		data = std::make_unique<Data>(this);
+	}
+	data->trap(index, this);
+}
+
+SpirvShader::Impl::Debugger::State::Data::Data(State *state)
+{
+	buildGlobals(state);
+
+	thread = state->debugger->ctx->lock().currentThread();
+
+	if(!state->debugger->shaderHasDebugInfo)
+	{
+		// Enter the stack frame entry for the SPIR-V.
+		thread->enter(state->debugger->spirvFile, "SPIR-V", [&](vk::dbg::Frame &frame) {
+			for(size_t lane = 0; lane < sw::SIMD::Width; lane++)
 			{
-				case 1:
-					group.put<Key, float>(key, Extract(val.Float(0), lane));
-					break;
-				case 2:
-					group.put<Key, float>(key, Extract(val.Float(0), lane), Extract(val.Float(1), lane));
-					break;
-				case 3:
-					group.put<Key, float>(key, Extract(val.Float(0), lane), Extract(val.Float(1), lane), Extract(val.Float(2), lane));
-					break;
-				case 4:
-					group.put<Key, float>(key, Extract(val.Float(0), lane), Extract(val.Float(1), lane), Extract(val.Float(2), lane), Extract(val.Float(3), lane));
-					break;
-				default:
+				auto laneLocals = std::make_shared<vk::dbg::Struct>("Lane", globals.lanes[lane]);
+				frame.locals->variables->put(laneName(lane), laneLocals);
+				frame.hovers->variables->extend(std::make_shared<HoversFromLocals>(frame.locals->variables));
+			}
+		});
+	}
+}
+
+void SpirvShader::Impl::Debugger::State::Data::terminate(State *state)
+{
+	if(state->debugger->shaderHasDebugInfo)
+	{
+		for(size_t i = 0; i < stack.size(); i++)
+		{
+			thread->exit();
+		}
+	}
+	else
+	{
+		thread->exit();
+	}
+}
+
+void SpirvShader::Impl::Debugger::State::Data::trap(int index, State *state)
+{
+	auto debugger = state->debugger;
+
+	// Update the thread frames from the stack of scopes
+	auto const &locationAndScope = debugger->traps.byIndex[index];
+
+	if(locationAndScope.scope)
+	{
+		// Gather the new stack as LexicalBlocks.
+		std::vector<StackEntry> newStack;
+		if(auto block = debug::find<debug::LexicalBlock>(locationAndScope.scope->scope))
+		{
+			newStack.emplace_back(StackEntry{ block, block->line });
+		}
+		for(auto inlined = locationAndScope.scope->inlinedAt; inlined != nullptr; inlined = inlined->inlined)
+		{
+			if(auto block = debug::find<debug::LexicalBlock>(inlined->scope))
+			{
+				newStack.emplace_back(StackEntry{ block, inlined->line });
+			}
+		}
+		std::reverse(newStack.begin(), newStack.end());
+
+		// shrink pop stack frames until stack length is at most maxLen.
+		auto shrink = [&](size_t maxLen) {
+			while(stack.size() > maxLen)
+			{
+				thread->exit();
+				stack.pop_back();
+			}
+		};
+
+		// Pop stack frames until stack length is at most newStack length.
+		shrink(newStack.size());
+
+		// Find first deviation in stack frames, and shrink to that point.
+		// Special care is taken for deviation in just the top most frame so we
+		// don't end up reconstructing the top most stack frame every scope
+		// change.
+		for(size_t i = 0; i < stack.size(); i++)
+		{
+			if(stack[i] != newStack[i])
+			{
+				bool isTopMostFrame = i == (newStack.size() - 1);
+				auto oldFunction = debug::find<debug::Function>(stack[i].block);
+				auto newFunction = debug::find<debug::Function>(newStack[i].block);
+				if(isTopMostFrame && oldFunction == newFunction)
 				{
-					auto vec = group.group<Key>(key);
-					for(uint32_t i = 0; i < count; i++)
-					{
-						vec.template put<int, float>(i, Extract(val.Float(i), lane));
-					}
+					// Deviation is just a movement in the top most frame's
+					// function.
+					// Don't exit() and enter() for the same function - it'll
+					// be treated as a step out and step in, breaking stepping
+					// commands. Instead, just update the frame variables for
+					// the new scope.
+					stack[i].block = newStack[i].block;
+					thread->update(true, [&](vk::dbg::Frame &frame) {
+						updateFrameLocals(state, frame, stack[i].block);
+					});
+				}
+				else
+				{
+					shrink(i);
 				}
 				break;
 			}
 		}
-		break;
-		case spv::OpTypePointer:
+
+		// Now rebuild the parts of stack frames that are new
+		for(size_t i = stack.size(); i < newStack.size(); i++)
 		{
-			auto objectTy = shader->getType(shader->getObject(id));
-			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, lane));
+			auto entry = newStack[i];
+			stack.emplace_back(entry);
+			auto function = debug::find<debug::Function>(entry.block);
+			thread->enter(entry.block->source->dbgFile, function->name, [&](vk::dbg::Frame &frame) {
+				frame.location = vk::dbg::Location{ function->source->dbgFile, (int)entry.line };
+				frame.hovers->variables->extend(std::make_shared<HoversFromLocals>(frame.locals->variables));
+				updateFrameLocals(state, frame, entry.block);
 			});
 		}
-		break;
+	}
+
+	// If the debugger thread is running, notify that we're pausing due to the
+	// trap.
+	if(thread->state() == vk::dbg::Thread::State::Running)
+	{
+		// pause() changes the thread state Paused, and will cause the next
+		// frame location changing call update() to block until the debugger
+		// instructs the thread to resume or step.
+		thread->pause();
+		debugger->ctx->serverEventBroadcast()->onLineBreakpointHit(thread->id);
+	}
+
+	// Update the frame location. This will likely block until the debugger
+	// instructs the thread to resume or step.
+	thread->update(true, [&](vk::dbg::Frame &frame) {
+		frame.location = locationAndScope.location;
+	});
+
+	// Clear the alwaysTrap state if the debugger instructed the thread to
+	// resume, or set it if we're single line stepping (so we can keep track of
+	// location).
+	state->alwaysTrap = thread->state() != vk::dbg::Thread::State::Running;
+}
+
+void SpirvShader::Impl::Debugger::State::Data::updateFrameLocals(State *state, vk::dbg::Frame &frame, debug::LexicalBlock *block)
+{
+	auto locals = getOrCreateLocals(state, block);
+	for(size_t lane = 0; lane < sw::SIMD::Width; lane++)
+	{
+		auto laneLocals = std::make_shared<vk::dbg::Struct>("Lane", locals[lane]);
+		frame.locals->variables->put(laneName(lane), laneLocals);
+	}
+}
+
+SpirvShader::Impl::Debugger::State::Data::PerLaneVariables
+SpirvShader::Impl::Debugger::State::Data::getOrCreateLocals(State *state, debug::LexicalBlock const *block)
+{
+	return getOrCreate(locals, block, [&] {
+		PerLaneVariables locals;
+		for(int lane = 0; lane < sw::SIMD::Width; lane++)
+		{
+			auto vc = std::make_shared<vk::dbg::VariableContainer>();
+
+			for(auto var : block->variables)
+			{
+				auto name = var->name;
+
+				switch(var->definition)
+				{
+					case debug::LocalVariable::Definition::Undefined:
+					{
+						vc->put(name, var->type->undefined());
+						break;
+					}
+					case debug::LocalVariable::Definition::Declaration:
+					{
+						auto data = state->debugger->shadow.get(state, var->declaration->variable, lane);
+						vc->put(name, var->type->value(data.dref(lane), true));
+						break;
+					}
+					case debug::LocalVariable::Definition::Values:
+					{
+						vc->put(name, std::make_shared<LocalVariableValue>(var, state, lane));
+						break;
+					}
+				}
+			}
+
+			locals[lane] = std::move(vc);
+		}
+		if(auto parent = debug::find<debug::LexicalBlock>(block->parent))
+		{
+			auto extend = getOrCreateLocals(state, parent);
+			for(int lane = 0; lane < sw::SIMD::Width; lane++)
+			{
+				locals[lane]->extend(extend[lane]);
+			}
+		}
+		else
+		{
+			for(int lane = 0; lane < sw::SIMD::Width; lane++)
+			{
+				locals[lane]->extend(globals.lanes[lane]);
+			}
+		}
+		return locals;
+	});
+}
+
+template<typename T>
+void SpirvShader::Impl::Debugger::State::Data::buildGlobal(const char *name, const T &val)
+{
+	globals.common->put(name, makeDbgValue(val));
+}
+
+template<typename T, int N>
+void SpirvShader::Impl::Debugger::State::Data::buildGlobal(const char *name, const sw::SIMD::PerLane<T, N> &simd)
+{
+	for(int lane = 0; lane < sw::SIMD::Width; lane++)
+	{
+		for(int i = 0; i < N; i++)
+		{
+			globals.lanes[lane]->put(name, makeDbgValue(simd[lane]));
+		}
+	}
+}
+
+void SpirvShader::Impl::Debugger::State::Data::buildGlobals(State *state)
+{
+	globals.common = std::make_shared<vk::dbg::VariableContainer>();
+	globals.common->put("subgroupSize", vk::dbg::make_reference(state->globals.subgroupSize));
+
+	for(int lane = 0; lane < sw::SIMD::Width; lane++)
+	{
+		auto vc = std::make_shared<vk::dbg::VariableContainer>();
+
+		vc->put("enabled", vk::dbg::make_reference(reinterpret_cast<const bool &>(state->globals.activeLaneMask[lane])));
+
+		for(auto &it : state->debugger->objects)
+		{
+			if(auto var = debug::cast<debug::GlobalVariable>(it.second.get()))
+			{
+				if(var->variable != 0)
+				{
+					auto data = state->debugger->shadow.get(state, var->variable, lane);
+					vc->put(var->name, var->type->value(data.dref(lane), true));
+				}
+			}
+		}
+
+		auto spirv = buildSpirvVariables(state, lane);
+		if(state->debugger->shaderHasDebugInfo)
+		{
+			vc->put("SPIR-V", spirv);
+		}
+		else
+		{
+			vc->extend(spirv->children());
+		}
+
+		vc->extend(globals.common);
+		globals.lanes[lane] = vc;
+	}
+
+	switch(state->debugger->shader->executionModel)
+	{
+		case spv::ExecutionModelGLCompute:
+		{
+			buildGlobal("numWorkgroups", state->globals.compute.numWorkgroups);
+			buildGlobal("workgroupID", state->globals.compute.workgroupID);
+			buildGlobal("workgroupSize", state->globals.compute.workgroupSize);
+			buildGlobal("numSubgroups", state->globals.compute.numSubgroups);
+			buildGlobal("subgroupIndex", state->globals.compute.subgroupIndex);
+			buildGlobal("globalInvocationId", state->globals.compute.globalInvocationId);
+			buildGlobal("localInvocationIndex", state->globals.compute.localInvocationIndex);
+			break;
+		}
+		case spv::ExecutionModelFragment:
+		{
+			buildGlobal("viewIndex", state->globals.fragment.viewIndex);
+			buildGlobal("fragCoord", state->globals.fragment.fragCoord);
+			buildGlobal("pointCoord", state->globals.fragment.pointCoord);
+			buildGlobal("windowSpacePosition", state->globals.fragment.windowSpacePosition);
+			buildGlobal("helperInvocation", state->globals.fragment.helperInvocation);
+			break;
+		}
+		case spv::ExecutionModelVertex:
+		{
+			buildGlobal("viewIndex", state->globals.vertex.viewIndex);
+			buildGlobal("instanceIndex", state->globals.vertex.instanceIndex);
+			buildGlobal("vertexIndex", state->globals.vertex.vertexIndex);
+			break;
+		}
 		default:
 			break;
 	}
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// sw::SpirvShader
-////////////////////////////////////////////////////////////////////////////////
-void SpirvShader::dbgInit(const std::shared_ptr<vk::dbg::Context> &dbgctx)
+std::shared_ptr<vk::dbg::Struct>
+SpirvShader::Impl::Debugger::State::Data::buildSpirvVariables(State *state, int lane) const
 {
-	impl.debugger = new Impl::Debugger();
-	impl.debugger->ctx = dbgctx;
+	return vk::dbg::Struct::create("SPIR-V", [&](auto &vc) {
+		auto debugger = state->debugger;
+		auto &entries = debugger->shadow.entries;
+		std::vector<Object::ID> ids;
+		ids.reserve(entries.size());
+		for(auto it : entries)
+		{
+			ids.emplace_back(it.first);
+		}
+		std::sort(ids.begin(), ids.end());
+		for(auto id : ids)
+		{
+			auto &obj = debugger->shader->getObject(id);
+			auto &objTy = debugger->shader->getType(obj.typeId());
+			auto name = "%" + std::to_string(id.value());
+			auto memory = debugger->shadow.get(state, id, lane);
+			switch(obj.kind)
+			{
+				case Object::Kind::Intermediate:
+				case Object::Kind::Constant:
+					if(auto val = buildSpirvValue(state, memory, objTy, lane))
+					{
+						vc->put(name, val);
+					}
+					break;
+				default:
+					break;  // Not handled yet.
+			}
+		}
+	});
+}
+
+std::shared_ptr<vk::dbg::Value>
+SpirvShader::Impl::Debugger::State::Data::buildSpirvValue(State *state, Shadow::Memory memory, const SpirvShader::Type &type, int lane) const
+{
+	auto debugger = state->debugger;
+	auto shader = debugger->shader;
+
+	switch(type.definition.opcode())
+	{
+		case spv::OpTypeInt:
+			return vk::dbg::make_reference(reinterpret_cast<uint32_t *>(memory.addr)[lane]);
+		case spv::OpTypeFloat:
+			return vk::dbg::make_reference(reinterpret_cast<float *>(memory.addr)[lane]);
+		case spv::OpTypeVector:
+		{
+			auto elTy = shader->getType(type.element);
+			return vk::dbg::Struct::create("vector", [&](auto &fields) {
+				for(uint32_t i = 0; i < type.componentCount; i++)
+				{
+					if(auto val = buildSpirvValue(state, memory, elTy, lane))
+					{
+						fields->put(vecElementName(i, type.componentCount), val);
+						memory.addr += sizeof(uint32_t) * sw::SIMD::Width;
+					}
+				}
+			});
+		}
+		default:
+			return nullptr;  // Not handled yet
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// sw::SpirvShader methods
+////////////////////////////////////////////////////////////////////////////////
+void SpirvShader::dbgInit(const std::shared_ptr<vk::dbg::Context> &ctx)
+{
+	impl.debugger = new Impl::Debugger(this, ctx);
 }
 
 void SpirvShader::dbgTerm()
@@ -1957,7 +2502,7 @@
 		default: name = "SPIR-V Shader"; break;
 	}
 	static std::atomic<int> id = { 0 };
-	name += tostring(id++) + ".spvasm";
+	name += std::to_string(id++) + ".spvasm";
 	dbg->spirvFile = dbg->ctx->lock().createVirtualFile(name.c_str(), source.c_str());
 }
 
@@ -1966,19 +2511,11 @@
 	auto dbg = impl.debugger;
 	if(!dbg) { return; }
 
-	using Group = Impl::Debugger::Group;
+	dbg->shaderHasDebugInfo = extensionsImported.count(Extension::OpenCLDebugInfo100) > 0;
 
 	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);
+	auto dbgState = rr::Call(&Impl::Debugger::State::create, dbg);
 
 	routine->dbgState = dbgState;
 
@@ -1986,52 +2523,43 @@
 
 	for(int i = 0; i < SIMD::Width; i++)
 	{
-		auto globals = Group::globals(dbgState, i);
-		globals.put<const char *, int>("subgroupSize", routine->invocationsPerSubgroup);
+		using Globals = Impl::Debugger::State::Globals;
+
+		auto globals = dbgState + OFFSET(Impl::Debugger::State, globals);
+		store(globals + OFFSET(Globals, subgroupSize), routine->invocationsPerSubgroup);
 
 		switch(executionModel)
 		{
 			case spv::ExecutionModelGLCompute:
-				globals.putVec3<const char *, int>("numWorkgroups", routine->numWorkgroups);
-				globals.putVec3<const char *, int>("workgroupID", routine->workgroupID);
-				globals.putVec3<const char *, int>("workgroupSize", routine->workgroupSize);
-				globals.put<const char *, int>("numSubgroups", routine->subgroupsPerWorkgroup);
-				globals.put<const char *, int>("subgroupIndex", routine->subgroupIndex);
-
-				globals.put<const char *, int>("globalInvocationId",
-				                               rr::Extract(routine->globalInvocationID[0], i),
-				                               rr::Extract(routine->globalInvocationID[1], i),
-				                               rr::Extract(routine->globalInvocationID[2], i));
-				globals.put<const char *, int>("localInvocationId",
-				                               rr::Extract(routine->localInvocationID[0], i),
-				                               rr::Extract(routine->localInvocationID[1], i),
-				                               rr::Extract(routine->localInvocationID[2], i));
-				globals.put<const char *, int>("localInvocationIndex", rr::Extract(routine->localInvocationIndex, i));
+			{
+				auto compute = globals + OFFSET(Globals, compute);
+				store(compute + OFFSET(Globals::Compute, numWorkgroups), routine->numWorkgroups);
+				store(compute + OFFSET(Globals::Compute, workgroupID), routine->workgroupID);
+				store(compute + OFFSET(Globals::Compute, workgroupSize), routine->workgroupSize);
+				store(compute + OFFSET(Globals::Compute, numSubgroups), routine->subgroupsPerWorkgroup);
+				store(compute + OFFSET(Globals::Compute, subgroupIndex), routine->subgroupIndex);
+				store(compute + OFFSET(Globals::Compute, globalInvocationId), routine->globalInvocationID);
+				store(compute + OFFSET(Globals::Compute, localInvocationIndex), routine->localInvocationIndex);
 				break;
-
+			}
 			case spv::ExecutionModelFragment:
-				globals.put<const char *, int>("viewIndex", routine->viewID);
-				globals.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));
-				globals.put<const char *, float>("pointCoord",
-				                                 rr::Extract(routine->pointCoord[0], i),
-				                                 rr::Extract(routine->pointCoord[1], i));
-				globals.put<const char *, int>("windowSpacePosition",
-				                               rr::Extract(routine->windowSpacePosition[0], i),
-				                               rr::Extract(routine->windowSpacePosition[1], i));
-				globals.put<const char *, int>("helperInvocation", rr::Extract(routine->helperInvocation, i));
+			{
+				auto fragment = globals + OFFSET(Globals, fragment);
+				store(fragment + OFFSET(Globals::Fragment, viewIndex), routine->viewID);
+				store(fragment + OFFSET(Globals::Fragment, fragCoord), routine->fragCoord);
+				store(fragment + OFFSET(Globals::Fragment, pointCoord), routine->pointCoord);
+				store(fragment + OFFSET(Globals::Fragment, windowSpacePosition), routine->windowSpacePosition);
+				store(fragment + OFFSET(Globals::Fragment, helperInvocation), routine->helperInvocation);
 				break;
-
+			}
 			case spv::ExecutionModelVertex:
-				globals.put<const char *, int>("viewIndex", routine->viewID);
-				globals.put<const char *, int>("instanceIndex", routine->instanceID);
-				globals.put<const char *, int>("vertexIndex",
-				                               rr::Extract(routine->vertexIndex, i));
+			{
+				auto vertex = globals + OFFSET(Globals, vertex);
+				store(vertex + OFFSET(Globals::Vertex, viewIndex), routine->viewID);
+				store(vertex + OFFSET(Globals::Vertex, instanceIndex), routine->instanceID);
+				store(vertex + OFFSET(Globals::Vertex, vertexIndex), routine->vertexIndex);
 				break;
-
+			}
 			default:
 				break;
 		}
@@ -2043,6 +2571,8 @@
 	auto dbg = impl.debugger;
 	if(!dbg) { return; }
 
+	dbg->finalize();
+
 	rr::Call(&Impl::Debugger::State::destroy, state->routine->dbgState);
 }
 
@@ -2085,17 +2615,16 @@
 			// For example:
 			//   while(true) { foo(); }
 			// foo() should be repeatedly steppable.
-			dbg->setNextSetLocationIsStep();
+			dbg->setNextSetLocationIsSteppable();
 		}
 
-		if(extensionsImported.count(Extension::OpenCLDebugInfo100) == 0)
+		if(!dbg->shaderHasDebugInfo)
 		{
 			// We're emitting debugger logic for SPIR-V.
 			if(IsStatement(insn.opcode()))
 			{
 				auto line = dbg->spirvLineMappings.at(insn.wordPointer(0));
-				auto column = 0;
-				dbg->setLocation(state, dbg->spirvFile, line, column);
+				dbg->setLocation(state, dbg->spirvFile, line);
 			}
 		}
 	}
@@ -2106,38 +2635,35 @@
 	auto dbg = impl.debugger;
 	if(!dbg) { return; }
 
-	// Don't display SSA values if rich debug info is available
-	if(extensionsImported.count(Extension::OpenCLDebugInfo100) == 0)
+	switch(insn.opcode())
 	{
-		// We're emitting debugger logic for SPIR-V.
-		// Does this instruction emit a result that should be exposed to the
-		// debugger?
-		auto resIt = dbg->results.find(insn.wordPointer(0));
-		if(resIt != dbg->results.end())
+		case spv::OpVariable:
+		case spv::OpConstant:  // TODO: Move constants out of shadow memory.
+		case spv::OpConstantNull:
+		case spv::OpConstantTrue:
+		case spv::OpConstantFalse:
+		case spv::OpConstantComposite:
+			dbg->shadow.create(this, state, insn.resultId());
+			break;
+		default:
 		{
-			auto id = resIt->second;
-			dbgExposeIntermediate(id, state);
+			auto resIt = dbg->results.find(insn.wordPointer(0));
+			if(resIt != dbg->results.end())
+			{
+				dbg->shadow.create(this, state, resIt->second);
+			}
 		}
 	}
 }
 
-void SpirvShader::dbgExposeIntermediate(Object::ID id, EmitState *state) const
-{
-	auto dbg = impl.debugger;
-	if(!dbg) { return; }
-
-	dbg->exposeVariable(this, id, &debug::Scope::Global, nullptr, 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);
-	}
+	auto dbgState = state->routine->dbgState;
+	auto globals = dbgState + OFFSET(Impl::Debugger::State, globals);
+	store(globals + OFFSET(Impl::Debugger::State::Globals, activeLaneMask), mask);
 }
 
 void SpirvShader::dbgDeclareResult(const InsnIterator &insn, Object::ID resultId) const
@@ -2154,8 +2680,7 @@
 	{
 		auto path = getString(insn.word(1));
 		auto line = insn.word(2);
-		auto column = insn.word(3);
-		dbg->setLocation(state, path, line, column);
+		dbg->setLocation(state, path.c_str(), line);
 	}
 	return EmitResult::Continue;
 }
@@ -2178,14 +2703,14 @@
 	auto dbg = impl.debugger;
 	if(!dbg) { return; }
 
-	dbg->process(this, insn, nullptr, Impl::Debugger::Pass::Define);
+	dbg->process(insn, nullptr, Impl::Debugger::Pass::Define);
 }
 
 SpirvShader::EmitResult SpirvShader::EmitOpenCLDebugInfo100(InsnIterator insn, EmitState *state) const
 {
 	if(auto dbg = impl.debugger)
 	{
-		dbg->process(this, insn, state, Impl::Debugger::Pass::Emit);
+		dbg->process(insn, state, Impl::Debugger::Pass::Emit);
 	}
 	return EmitResult::Continue;
 }