SpirvShaderDebugger: Implement OpenCL.Debug.100

This is a first, rough, implementation of the OpenCL.Debug.100 extension instruction set.

This is incomplete, and likely buggy.

It does however implement enough to perform single line stepping through hand-crafted HLSL, with basic variable inspection.

Many tests and fixes to come...

Bug: b/145351270
Change-Id: Ia570f5b22a8b3ce39df0438ca3c6700d1a00d014
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/40268
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Kokoro-Presubmit: 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 7c45d13..9b4ce13 100644
--- a/src/Pipeline/SpirvShaderDebugger.cpp
+++ b/src/Pipeline/SpirvShaderDebugger.cpp
@@ -24,10 +24,31 @@
 #	include "Vulkan/Debug/Thread.hpp"
 #	include "Vulkan/Debug/Variable.hpp"
 
+#	include "spirv-tools/ext/OpenCLDebugInfo100.h"
 #	include "spirv-tools/libspirv.h"
 
 #	include <algorithm>
 
+namespace {
+
+// ArgTy<F>::type resolves to the single argument type of the function F.
+template<typename F>
+struct ArgTy
+{
+	using type = typename ArgTy<decltype(&F::operator())>::type;
+};
+
+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;
+
+}  // anonymous namespace
+
 namespace spvtools {
 
 // Function implemented in third_party/SPIRV-Tools/source/disassemble.cpp
@@ -54,6 +75,10 @@
 {
 	return std::to_string(s);
 }
+std::string tostring(char *s)
+{
+	return s;
+}
 std::string tostring(const char *s)
 {
 	return s;
@@ -63,6 +88,258 @@
 	return "%" + std::to_string(id.value());
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// OpenCL.Debug.100 data structures
+////////////////////////////////////////////////////////////////////////////////
+namespace debug {
+
+struct Member;
+
+struct Object
+{
+	enum class Kind
+	{
+		Object,
+		Declare,
+		Expression,
+		Function,
+		InlinedAt,
+		LocalVariable,
+		Member,
+		Operation,
+		Source,
+		SourceScope,
+		Value,
+
+		// Scopes
+		CompilationUnit,
+		LexicalBlock,
+
+		// Types
+		BasicType,
+		VectorType,
+		FunctionType,
+		CompositeType,
+	};
+
+	using ID = sw::SpirvID<Object>;
+	static constexpr auto KIND = Kind::Object;
+	inline Object(Kind kind)
+	    : kind(kind)
+	{}
+	const Kind kind;
+
+	// kindof() returns true iff kind is of this type, or any type deriving from
+	// this type.
+	static constexpr bool kindof(Object::Kind kind) { return true; }
+};
+
+template<typename TYPE_, typename BASE, Object::Kind KIND_>
+struct ObjectImpl : public BASE
+{
+	using ID = sw::SpirvID<TYPE_>;
+	static constexpr auto KIND = KIND_;
+
+	ObjectImpl()
+	    : 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; }
+};
+
+template<typename TO, typename FROM>
+TO *cast(FROM *obj)
+{
+	return (TO::kindof(obj->kind)) ? static_cast<TO *>(obj) : nullptr;
+}
+
+template<typename TO, typename FROM>
+const TO *cast(const FROM *obj)
+{
+	return (TO::kindof(obj->kind)) ? static_cast<const TO *>(obj) : nullptr;
+}
+
+struct Scope : public Object
+{
+	// Root represents the root stack frame scope.
+	static const Scope Root;
+
+	using ID = sw::SpirvID<Scope>;
+	inline Scope(Kind kind)
+	    : Object(kind)
+	{}
+
+	// kindof() returns true iff kind is of this type, or any type deriving from
+	// this type.
+	static constexpr bool kindof(Kind kind)
+	{
+		return kind == Kind::CompilationUnit ||
+		       kind == Kind::LexicalBlock;
+	}
+
+	struct Source *source = nullptr;
+};
+
+struct Type : public Object
+{
+	using ID = sw::SpirvID<Type>;
+	inline Type(Kind kind)
+	    : Object(kind)
+	{}
+
+	// kindof() returns true iff kind is of this type, or any type deriving from
+	// this type.
+	static constexpr bool kindof(Kind kind)
+	{
+		return kind == Kind::BasicType ||
+		       kind == Kind::VectorType ||
+		       kind == Kind::FunctionType ||
+		       kind == Kind::CompositeType;
+	}
+};
+
+struct CompilationUnit : ObjectImpl<CompilationUnit, Scope, Object::Kind::CompilationUnit>
+{
+};
+
+struct Source : ObjectImpl<Source, Object, Object::Kind::Source>
+{
+	spv::SourceLanguage language;
+	uint32_t version = 0;
+	std::string file;
+	std::string source;
+
+	std::shared_ptr<vk::dbg::File> dbgFile;
+};
+
+struct BasicType : ObjectImpl<BasicType, Type, Object::Kind::BasicType>
+{
+	std::string name;
+	uint32_t size = 0;  // in bits.
+	OpenCLDebugInfo100DebugBaseTypeAttributeEncoding encoding = OpenCLDebugInfo100Unspecified;
+};
+
+struct VectorType : ObjectImpl<VectorType, Type, Object::Kind::VectorType>
+{
+	Type *base = nullptr;
+	uint32_t components = 0;
+};
+
+struct FunctionType : ObjectImpl<FunctionType, Type, Object::Kind::FunctionType>
+{
+	uint32_t flags = 0;  // OR'd from OpenCLDebugInfo100DebugInfoFlags
+	Type *returnTy = nullptr;
+	std::vector<Type *> paramTys;
+};
+
+struct CompositeType : ObjectImpl<CompositeType, Type, Object::Kind::CompositeType>
+{
+	std::string name;
+	OpenCLDebugInfo100DebugCompositeType tag = OpenCLDebugInfo100Class;
+	Source *source = nullptr;
+	uint32_t line = 0;
+	uint32_t column = 0;
+	Object *parent = nullptr;
+	std::string linkage;
+	uint32_t size = 0;   // in bits.
+	uint32_t flags = 0;  // OR'd from OpenCLDebugInfo100DebugInfoFlags
+	std::vector<Member *> members;
+};
+
+struct Member : ObjectImpl<Member, Object, Object::Kind::Member>
+{
+	std::string name;
+	Type *type = nullptr;
+	Source *source = nullptr;
+	uint32_t line = 0;
+	uint32_t column = 0;
+	CompositeType *parent = nullptr;
+	uint32_t offset = 0;  // in bits
+	uint32_t size = 0;    // in bits
+	uint32_t flags = 0;   // OR'd from OpenCLDebugInfo100DebugInfoFlags
+};
+
+struct Function : ObjectImpl<Function, Object, Object::Kind::Function>
+{
+	std::string name;
+	FunctionType *type = nullptr;
+	Source *source = nullptr;
+	uint32_t line = 0;
+	uint32_t column = 0;
+	struct LexicalBlock *parent = nullptr;
+	std::string linkage;
+	uint32_t flags = 0;  // OR'd from OpenCLDebugInfo100DebugInfoFlags
+	uint32_t scopeLine = 0;
+	sw::SpirvShader::Function::ID function;
+};
+
+struct LexicalBlock : ObjectImpl<LexicalBlock, Scope, Object::Kind::LexicalBlock>
+{
+	uint32_t line = 0;
+	uint32_t column = 0;
+	Scope *parent = nullptr;
+	std::string name;
+	Function *function = nullptr;
+};
+
+struct InlinedAt : ObjectImpl<InlinedAt, Object, Object::Kind::InlinedAt>
+{
+	uint32_t line = 0;
+	Scope *scope = nullptr;
+	InlinedAt *inlined = nullptr;
+};
+
+struct SourceScope : ObjectImpl<SourceScope, Object, Object::Kind::SourceScope>
+{
+	LexicalBlock *scope = nullptr;
+	InlinedAt *inlinedAt = nullptr;
+};
+
+struct LocalVariable : ObjectImpl<LocalVariable, Object, Object::Kind::LocalVariable>
+{
+	static constexpr uint32_t NoArg = ~uint32_t(0);
+
+	std::string name;
+	Type *type = nullptr;
+	Source *source = nullptr;
+	uint32_t line = 0;
+	uint32_t column = 0;
+	Scope *parent = nullptr;
+	uint32_t arg = NoArg;
+};
+
+struct Operation : ObjectImpl<Operation, Object, Object::Kind::Operation>
+{
+	uint32_t opcode = 0;
+	std::vector<uint32_t> operands;
+};
+
+struct Expression : ObjectImpl<Expression, Object, Object::Kind::Expression>
+{
+	std::vector<Operation *> operations;
+};
+
+struct Declare : ObjectImpl<Declare, Object, Object::Kind::Declare>
+{
+	LocalVariable *local = nullptr;
+	sw::SpirvShader::Object::ID variable;
+	Expression *expression = nullptr;
+};
+
+struct Value : ObjectImpl<Value, Object, Object::Kind::Value>
+{
+	LocalVariable *local = nullptr;
+	sw::SpirvShader::Object::ID variable;
+	Expression *expression = nullptr;
+	std::vector<uint32_t> indexes;
+};
+
+const Scope Scope::Root = CompilationUnit{};
+
+}  // namespace debug
 }  // anonymous namespace
 
 namespace rr {
@@ -98,6 +375,14 @@
 	class Group;
 	class State;
 
+	enum class Pass
+	{
+		Define,
+		Emit
+	};
+
+	void process(const SpirvShader *shader, const InsnIterator &insn, EmitState *state, Pass pass);
+
 	void setPosition(EmitState *state, const std::string &path, uint32_t line, uint32_t column);
 
 	// exposeVariable exposes the variable with the given ID to the debugger
@@ -106,6 +391,8 @@
 	void exposeVariable(
 	    const SpirvShader *shader,
 	    const Key &key,
+	    const debug::Scope *scope,
+	    const debug::Type *type,
 	    Object::ID id,
 	    EmitState *state) const;
 
@@ -117,6 +404,7 @@
 	    const Group &group,
 	    int lane,
 	    const Key &key,
+	    const debug::Type *type,
 	    Object::ID id,
 	    EmitState *state,
 	    int wordOffset = 0) const;
@@ -127,7 +415,32 @@
 	std::unordered_map<const void *, Object::ID> results;     // instruction pointer to result ID
 
 private:
+	// add() registers the debug object with the given id.
+	template<typename ID, typename T>
+	void add(ID id, T *);
+
+	// get() returns the debug object with the given id.
+	// The object must exist and be of type (or derive from type) T.
+	template<typename T>
+	T *get(SpirvID<T> id) const;
+
+	// use get() and add() to access this
+	std::unordered_map<debug::Object::ID, std::unique_ptr<debug::Object>> objects;
+
 	std::unordered_map<std::string, vk::dbg::File::ID> fileIDs;
+
+	// defineOrEmit() when called in Pass::Define, creates and stores a
+	// zero-initialized object into the Debugger::objects map using the
+	// object identifier held by second instruction operand.
+	// When called in Pass::Emit, defineOrEmit() calls the function F with the
+	// previously-built object.
+	//
+	// F must be a function with the signature:
+	//   void(OBJECT_TYPE *)
+	//
+	// The object type is automatically inferred from the function signature.
+	template<typename F, typename T = typename std::remove_pointer<ArgTyT<F>>::type>
+	void defineOrEmit(InsnIterator insn, Pass pass, F &&emit);
 };
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -148,10 +461,12 @@
 	void exit();
 	void updateActiveLaneMask(int lane, bool enabled);
 	void update(vk::dbg::File::ID file, int line, int column);
+	void createScope(const debug::Scope *);
+	void setScope(debug::SourceScope *newScope);
 
-	vk::dbg::VariableContainer *locals();
-	vk::dbg::VariableContainer *hovers();
-	vk::dbg::VariableContainer *localsLane(int lane);
+	vk::dbg::VariableContainer *locals(const debug::Scope *);
+	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);
@@ -159,6 +474,11 @@
 	template<typename K, typename V>
 	void putVal(vk::dbg::VariableContainer *vc, K key, V value);
 
+	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
 	{
 		std::shared_ptr<vk::dbg::Scope> locals;
@@ -166,9 +486,14 @@
 		std::array<std::shared_ptr<vk::dbg::VariableContainer>, sw::SIMD::Width> localsByLane;
 	};
 
+	// getScopes() returns the Scopes object for the given debug::Scope.
+	const Scopes &getScopes(const debug::Scope *scope);
+
 	const Debugger *debugger;
 	const std::shared_ptr<vk::dbg::Thread> thread;
-	Scopes threadScopes;
+	std::unordered_map<const debug::Scope *, Scopes> scopes;
+	Scopes rootScopes;                       // Scopes for the root stack frame.
+	debug::SourceScope *srcScope = nullptr;  // Current source scope.
 };
 
 SpirvShader::Impl::Debugger::State *SpirvShader::Impl::Debugger::State::create(const Debugger *debugger, const char *name)
@@ -188,12 +513,12 @@
 {
 	enter(lock, stackBase);
 	thread->update([&](vk::dbg::Frame &frame) {
-		threadScopes.locals = frame.locals;
-		threadScopes.hovers = frame.hovers;
+		rootScopes.locals = frame.locals;
+		rootScopes.hovers = frame.hovers;
 		for(int i = 0; i < sw::SIMD::Width; i++)
 		{
-			threadScopes.localsByLane[i] = lock.createVariableContainer();
-			frame.locals->variables->put(laneNames[i], threadScopes.localsByLane[i]);
+			rootScopes.localsByLane[i] = lock.createVariableContainer();
+			frame.locals->variables->put(laneNames[i], rootScopes.localsByLane[i]);
 		}
 	});
 }
@@ -215,7 +540,7 @@
 
 void SpirvShader::Impl::Debugger::State::updateActiveLaneMask(int lane, bool enabled)
 {
-	threadScopes.localsByLane[lane]->put("enabled", vk::dbg::make_constant(enabled));
+	rootScopes.localsByLane[lane]->put("enabled", vk::dbg::make_constant(enabled));
 }
 
 void SpirvShader::Impl::Debugger::State::update(vk::dbg::File::ID fileID, int line, int column)
@@ -226,19 +551,19 @@
 	});
 }
 
-vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::locals()
+vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::locals(const debug::Scope *scope)
 {
-	return threadScopes.locals->variables.get();
+	return getScopes(scope).locals->variables.get();
 }
 
-vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::hovers()
+vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::hovers(const debug::Scope *scope)
 {
-	return threadScopes.hovers->variables.get();
+	return getScopes(scope).hovers->variables.get();
 }
 
-vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::localsLane(int i)
+vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::localsLane(const debug::Scope *scope, int i)
 {
-	return threadScopes.localsByLane[i].get();
+	return getScopes(scope).localsByLane[i].get();
 }
 
 template<typename K>
@@ -255,6 +580,67 @@
 	vc->put(tostring(key), vk::dbg::make_constant(value));
 }
 
+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)
+{
+	ASSERT(spirvScope != nullptr);
+
+	// TODO: Deal with scope nesting.
+
+	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++)
+	{
+		s.localsByLane[i] = lock.createVariableContainer();
+		s.locals->variables->put(laneNames[i], s.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;
+
+	auto lock = debugger->ctx->lock();
+	auto thread = lock.currentThread();
+
+	debug::Function *oldFunction = oldSrcScope ? oldSrcScope->scope->function : nullptr;
+	debug::Function *newFunction = newSrcScope ? newSrcScope->scope->function : nullptr;
+
+	if(oldFunction != newFunction)
+	{
+		if(oldFunction) { thread->exit(); }
+		if(newFunction) { thread->enter(lock, newFunction->source->dbgFile, newFunction->name); }
+	}
+
+	auto dbgScope = getScopes(srcScope->scope);
+	thread->update([&](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::Root)
+	{
+		return rootScopes;
+	}
+
+	auto dbgScopeIt = scopes.find(scope);
+	ASSERT_MSG(dbgScopeIt != scopes.end(), "createScope() not called for debug::Scope %p", scope);
+	return dbgScopeIt->second;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // sw::SpirvShader::Impl::Debugger::Group
 //
@@ -266,9 +652,9 @@
 public:
 	using Ptr = rr::Pointer<rr::Byte>;
 
-	static Group hovers(Ptr state);
-	static Group locals(Ptr state);
-	static Group localsLane(Ptr state, int lane);
+	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);
 
 	Group(Ptr state, Ptr group);
 
@@ -278,6 +664,9 @@
 	template<typename K, typename V, typename RK, typename RV>
 	void put(RK key, RV value) 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;
 
@@ -296,21 +685,21 @@
 };
 
 SpirvShader::Impl::Debugger::Group
-SpirvShader::Impl::Debugger::Group::hovers(Ptr state)
+SpirvShader::Impl::Debugger::Group::hovers(Ptr state, const debug::Scope *scope)
 {
-	return Group(state, rr::Call(&State::hovers, state));
+	return Group(state, rr::Call(&State::hovers, state, scope));
 }
 
 SpirvShader::Impl::Debugger::Group
-SpirvShader::Impl::Debugger::Group::locals(Ptr state)
+SpirvShader::Impl::Debugger::Group::locals(Ptr state, const debug::Scope *scope)
 {
-	return Group(state, rr::Call(&State::locals, state));
+	return Group(state, rr::Call(&State::locals, state, scope));
 }
 
 SpirvShader::Impl::Debugger::Group
-SpirvShader::Impl::Debugger::Group::localsLane(Ptr state, int lane)
+SpirvShader::Impl::Debugger::Group::localsLane(Ptr state, const debug::Scope *scope, int lane)
 {
-	return Group(state, rr::Call(&State::localsLane, state, lane));
+	return Group(state, rr::Call(&State::localsLane, state, scope, lane));
 }
 
 SpirvShader::Impl::Debugger::Group::Group(Ptr state, Ptr group)
@@ -330,6 +719,12 @@
 	rr::Call(&State::putVal<K, V>, state, ptr, key, value);
 }
 
+template<typename K, typename V, typename RK>
+void SpirvShader::Impl::Debugger::Group::putRef(RK key, RValue<Pointer<Byte>> ref) const
+{
+	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
 {
@@ -369,6 +764,201 @@
 ////////////////////////////////////////////////////////////////////////////////
 // sw::SpirvShader::Impl::Debugger methods
 ////////////////////////////////////////////////////////////////////////////////
+template<typename F, typename T>
+void SpirvShader::Impl::Debugger::defineOrEmit(InsnIterator insn, Pass pass, F &&emit)
+{
+	auto id = SpirvID<T>(insn.word(2));
+	switch(pass)
+	{
+		case Pass::Define:
+			add(id, new T());
+			break;
+		case Pass::Emit:
+			emit(get<T>(id));
+			break;
+	}
+}
+
+void SpirvShader::Impl::Debugger::process(const SpirvShader *shader, const InsnIterator &insn, EmitState *state, Pass pass)
+{
+	auto dbg = shader->impl.debugger;
+	auto extInstIndex = insn.word(4);
+	switch(extInstIndex)
+	{
+		case OpenCLDebugInfo100DebugCompilationUnit:
+			defineOrEmit(insn, pass, [&](debug::CompilationUnit *cu) {
+				cu->source = get(debug::Source::ID(insn.word(7)));
+			});
+			break;
+		case OpenCLDebugInfo100DebugTypeBasic:
+			defineOrEmit(insn, pass, [&](debug::BasicType *type) {
+				type->name = shader->getString(insn.word(5));
+				type->size = shader->GetConstScalarInt(insn.word(6));
+				type->encoding = static_cast<OpenCLDebugInfo100DebugBaseTypeAttributeEncoding>(insn.word(7));
+			});
+			break;
+		case OpenCLDebugInfo100DebugTypeVector:
+			defineOrEmit(insn, pass, [&](debug::VectorType *type) {
+				type->base = get(debug::Type::ID(insn.word(5)));
+				type->components = insn.word(6);
+			});
+			break;
+		case OpenCLDebugInfo100DebugTypeFunction:
+			defineOrEmit(insn, pass, [&](debug::FunctionType *type) {
+				type->flags = insn.word(5);
+				type->returnTy = get(debug::Type::ID(insn.word(6)));
+				for(uint32_t i = 7; i < insn.wordCount(); i++)
+				{
+					type->paramTys.push_back(get(debug::Type::ID(insn.word(i))));
+				}
+			});
+			break;
+		case OpenCLDebugInfo100DebugTypeComposite:
+			defineOrEmit(insn, pass, [&](debug::CompositeType *type) {
+				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);
+				type->column = insn.word(9);
+				type->parent = get(debug::Object::ID(insn.word(10)));
+				type->linkage = shader->getString(insn.word(11));
+				type->size = shader->GetConstScalarInt(insn.word(12));
+				type->flags = insn.word(13);
+				for(uint32_t i = 14; i < insn.wordCount(); i++)
+				{
+					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);
+					}
+				}
+			});
+			break;
+		case OpenCLDebugInfo100DebugTypeMember:
+			defineOrEmit(insn, pass, [&](debug::Member *member) {
+				member->name = shader->getString(insn.word(5));
+				member->type = get(debug::Type::ID(insn.word(6)));
+				member->source = get(debug::Source::ID(insn.word(7)));
+				member->line = insn.word(8);
+				member->column = insn.word(9);
+				member->parent = get(debug::CompositeType::ID(insn.word(10)));
+				member->offset = shader->GetConstScalarInt(insn.word(11));
+				member->size = shader->GetConstScalarInt(insn.word(12));
+				member->flags = insn.word(13);
+			});
+			break;
+		case OpenCLDebugInfo100DebugFunction:
+			defineOrEmit(insn, pass, [&](debug::Function *func) {
+				func->name = shader->getString(insn.word(5));
+				func->type = get(debug::FunctionType::ID(insn.word(6)));
+				func->source = get(debug::Source::ID(insn.word(7)));
+				func->line = insn.word(8);
+				func->column = insn.word(9);
+				func->parent = get(debug::LexicalBlock::ID(insn.word(10)));
+				func->linkage = shader->getString(insn.word(11));
+				func->flags = insn.word(12);
+				func->scopeLine = insn.word(13);
+				func->function = Function::ID(insn.word(14));
+				// declaration: word(13)
+
+				func->parent->function = func;
+			});
+			break;
+		case OpenCLDebugInfo100DebugLexicalBlock:
+			defineOrEmit(insn, pass, [&](debug::LexicalBlock *scope) {
+				scope->source = get(debug::Source::ID(insn.word(5)));
+				scope->line = insn.word(6);
+				scope->column = insn.word(7);
+				scope->parent = get(debug::Scope::ID(insn.word(8)));
+				if(insn.wordCount() > 9)
+				{
+					scope->name = shader->getString(insn.word(9));
+				}
+
+				// TODO: We're creating scopes per-shader invocation.
+				// This is all really static information, and should only be created
+				// once *per program*.
+				rr::Call(&State::createScope, state->routine->dbgState, scope);
+			});
+			break;
+		case OpenCLDebugInfo100DebugScope:
+			defineOrEmit(insn, pass, [&](debug::SourceScope *ss) {
+				ss->scope = get(debug::LexicalBlock::ID(insn.word(5)));
+				if(insn.wordCount() > 6)
+				{
+					ss->inlinedAt = get(debug::InlinedAt::ID(insn.word(6)));
+				}
+
+				rr::Call(&State::setScope, state->routine->dbgState, ss);
+			});
+			break;
+		case OpenCLDebugInfo100DebugNoScope:
+			break;
+		case OpenCLDebugInfo100DebugLocalVariable:
+			defineOrEmit(insn, pass, [&](debug::LocalVariable *var) {
+				var->name = shader->getString(insn.word(5));
+				var->type = get(debug::Type::ID(insn.word(6)));
+				var->source = get(debug::Source::ID(insn.word(7)));
+				var->line = insn.word(8);
+				var->column = insn.word(9);
+				var->parent = get(debug::Scope::ID(insn.word(10)));
+				if(insn.wordCount() > 11)
+				{
+					var->arg = insn.word(11);
+				}
+			});
+			break;
+		case OpenCLDebugInfo100DebugDeclare:
+			defineOrEmit(insn, pass, [&](debug::Declare *decl) {
+				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);
+			});
+			break;
+		case OpenCLDebugInfo100DebugValue:
+			defineOrEmit(insn, pass, [&](debug::Value *value) {
+				value->local = get(debug::LocalVariable::ID(insn.word(5)));
+				value->variable = Object::ID(insn.word(6));
+				value->expression = get(debug::Expression::ID(insn.word(7)));
+				for(uint32_t i = 8; i < insn.wordCount(); i++)
+				{
+					value->indexes.push_back(insn.word(i));
+				}
+			});
+			break;
+		case OpenCLDebugInfo100DebugExpression:
+			defineOrEmit(insn, pass, [&](debug::Expression *expr) {
+				for(uint32_t i = 5; i < insn.wordCount(); i++)
+				{
+					expr->operations.push_back(get(debug::Operation::ID(insn.word(i))));
+				}
+			});
+			break;
+		case OpenCLDebugInfo100DebugSource:
+			defineOrEmit(insn, pass, [&](debug::Source *source) {
+				source->file = shader->getString(insn.word(5));
+				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());
+				source->dbgFile = file;
+				fileIDs.emplace(source->file.c_str(), file->id);
+			});
+			break;
+		default:
+			UNSUPPORTED("Unsupported OpenCLDebugInfo100 instruction %d", int(extInstIndex));
+	}
+}
+
 void SpirvShader::Impl::Debugger::setPosition(EmitState *state, const std::string &path, uint32_t line, uint32_t column)
 {
 	auto it = fileIDs.find(path);
@@ -378,19 +968,39 @@
 	}
 }
 
+template<typename ID, typename T>
+void SpirvShader::Impl::Debugger::add(ID id, T *obj)
+{
+	auto added = objects.emplace(debug::Object::ID(id.value()), obj).second;
+	ASSERT_MSG(added, "Debug object with %d already exists", id.value());
+}
+
+template<typename T>
+T *SpirvShader::Impl::Debugger::get(SpirvID<T> id) const
+{
+	auto it = objects.find(debug::Object::ID(id.value()));
+	ASSERT_MSG(it != objects.end(), "Unknown debug object %d", id.value());
+	auto ptr = debug::cast<T>(it->second.get());
+	ASSERT_MSG(ptr, "Debug object %d is not of the correct type. Got: %d, want: %d",
+	           id.value(), int(it->second->kind), int(T::KIND));
+	return ptr;
+}
+
 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
 {
 	auto dbgState = state->routine->dbgState;
-	auto hover = Group::hovers(dbgState).group<Key>(key);
+	auto hover = Group::hovers(dbgState, scope).group<Key>(key);
 	for(int lane = 0; lane < SIMD::Width; lane++)
 	{
-		exposeVariable(shader, Group::localsLane(dbgState, lane), lane, key, id, state);
-		exposeVariable(shader, hover, lane, laneNames[lane], id, state);
+		exposeVariable(shader, Group::localsLane(dbgState, scope, lane), lane, key, type, id, state);
+		exposeVariable(shader, hover, lane, laneNames[lane], type, id, state);
 	}
 }
 
@@ -400,10 +1010,146 @@
     const Group &group,
     int l,
     const Key &key,
+    const debug::Type *type,
     Object::ID id,
     EmitState *state,
     int wordOffset /* = 0 */) const
 {
+	if(type != nullptr)
+	{
+		if(auto ty = debug::cast<debug::BasicType>(type))
+		{
+			auto &obj = shader->getObject(id);
+			SIMD::Int val;
+			switch(obj.kind)
+			{
+				case Object::Kind::InterfaceVariable:
+				case Object::Kind::Pointer:
+				{
+					auto ptr = shader->GetPointerToData(id, 0, state) + sizeof(uint32_t) * wordOffset;
+					auto &ptrTy = shader->getType(obj.type);
+					if(IsStorageInterleavedByLane(ptrTy.storageClass))
+					{
+						ptr = InterleaveByLane(ptr);
+					}
+					auto addr = &ptr.base[Extract(ptr.offsets(), l)];
+					switch(ty->encoding)
+					{
+						case OpenCLDebugInfo100Address:
+							// TODO: This function takes a SIMD vector, and pointers cannot
+							// be held in them.
+							break;
+						case OpenCLDebugInfo100Boolean:
+							group.putRef<Key, bool>(key, addr);
+							break;
+						case OpenCLDebugInfo100Float:
+							group.putRef<Key, float>(key, addr);
+							break;
+						case OpenCLDebugInfo100Signed:
+							group.putRef<Key, int>(key, addr);
+							break;
+						case OpenCLDebugInfo100SignedChar:
+							group.putRef<Key, int8_t>(key, addr);
+							break;
+						case OpenCLDebugInfo100Unsigned:
+							group.putRef<Key, unsigned int>(key, addr);
+							break;
+						case OpenCLDebugInfo100UnsignedChar:
+							group.putRef<Key, uint8_t>(key, addr);
+							break;
+						default:
+							break;
+					}
+				}
+				break;
+				case Object::Kind::Constant:
+				case Object::Kind::Intermediate:
+				{
+					auto val = GenericValue(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, l) != 0);
+							break;
+						case OpenCLDebugInfo100Float:
+							group.put<Key, float>(key, Extract(As<SIMD::Float>(val), l));
+							break;
+						case OpenCLDebugInfo100Signed:
+							group.put<Key, int>(key, Extract(val, l));
+							break;
+						case OpenCLDebugInfo100SignedChar:
+							group.put<Key, int8_t>(key, SByte(Extract(val, l)));
+							break;
+						case OpenCLDebugInfo100Unsigned:
+							group.put<Key, unsigned int>(key, Extract(val, l));
+							break;
+						case OpenCLDebugInfo100UnsignedChar:
+							group.put<Key, uint8_t>(key, Byte(Extract(val, l)));
+							break;
+						default:
+							break;
+					}
+				}
+				break;
+				default:
+					break;
+			}
+			return;
+		}
+		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, l, "x", elTy, id, state, wordOffset + 0 * elWords);
+					break;
+				case 2:
+					exposeVariable(shader, vecGroup, l, "x", elTy, id, state, wordOffset + 0 * elWords);
+					exposeVariable(shader, vecGroup, l, "y", elTy, id, state, wordOffset + 1 * elWords);
+					break;
+				case 3:
+					exposeVariable(shader, vecGroup, l, "x", elTy, id, state, wordOffset + 0 * elWords);
+					exposeVariable(shader, vecGroup, l, "y", elTy, id, state, wordOffset + 1 * elWords);
+					exposeVariable(shader, vecGroup, l, "z", elTy, id, state, wordOffset + 2 * elWords);
+					break;
+				case 4:
+					exposeVariable(shader, vecGroup, l, "x", elTy, id, state, wordOffset + 0 * elWords);
+					exposeVariable(shader, vecGroup, l, "y", elTy, id, state, wordOffset + 1 * elWords);
+					exposeVariable(shader, vecGroup, l, "z", elTy, id, state, wordOffset + 2 * elWords);
+					exposeVariable(shader, vecGroup, l, "w", elTy, id, state, wordOffset + 3 * elWords);
+					break;
+				default:
+					for(uint32_t i = 0; i < ty->components; i++)
+					{
+						exposeVariable(shader, vecGroup, l, std::to_string(i).c_str(), elTy, id, state, wordOffset + i * elWords);
+					}
+					break;
+			}
+			return;
+		}
+		else if(auto ty = debug::cast<debug::CompositeType>(type))
+		{
+			auto objectGroup = group.group<Key>(key);
+
+			for(auto member : ty->members)
+			{
+				auto memberGroup = objectGroup.template group<const char *>(member->name.c_str());
+				exposeVariable(shader, memberGroup, l, member->name.c_str(), member->type, id, state, member->offset / 32);
+			}
+
+			return;
+		}
+	}
+
+	// No debug type information. Derive from SPIR-V.
 	GenericValue val(shader, state, id);
 	switch(shader->getType(val.type).opcode())
 	{
@@ -539,7 +1285,7 @@
 
 	SetActiveLaneMask(state->activeLaneMask(), state);
 
-	auto locals = Group::locals(dbgState);
+	auto locals = Group::locals(dbgState, &debug::Scope::Root);
 	locals.put<const char *, int>("subgroupSize", routine->invocationsPerSubgroup);
 
 	switch(executionModel)
@@ -553,7 +1299,7 @@
 
 			for(int i = 0; i < SIMD::Width; i++)
 			{
-				auto lane = Group::localsLane(dbgState, i);
+				auto lane = Group::localsLane(dbgState, &debug::Scope::Root, i);
 				lane.put<const char *, int>("globalInvocationId",
 				                            rr::Extract(routine->globalInvocationID[0], i),
 				                            rr::Extract(routine->globalInvocationID[1], i),
@@ -570,7 +1316,7 @@
 			locals.put<const char *, int>("viewIndex", routine->viewID);
 			for(int i = 0; i < SIMD::Width; i++)
 			{
-				auto lane = Group::localsLane(dbgState, i);
+				auto lane = Group::localsLane(dbgState, &debug::Scope::Root, i);
 				lane.put<const char *, float>("fragCoord",
 				                              rr::Extract(routine->fragCoord[0], i),
 				                              rr::Extract(routine->fragCoord[1], i),
@@ -612,12 +1358,15 @@
 	printf("%s\n", instruction.c_str());
 #	endif  // PRINT_EACH_PROCESSED_INSTRUCTION
 
-	auto dbg = impl.debugger;
-	if(!dbg) { return; }
+	if(extensionsImported.count(Extension::OpenCLDebugInfo100) == 0)
+	{
+		auto dbg = impl.debugger;
+		if(!dbg) { return; }
 
-	auto line = dbg->spirvLineMappings.at(insn.wordPointer(0));
-	auto column = 0;
-	rr::Call(&Impl::Debugger::State::update, state->routine->dbgState, dbg->spirvFile->id, line, column);
+		auto line = dbg->spirvLineMappings.at(insn.wordPointer(0));
+		auto column = 0;
+		rr::Call(&Impl::Debugger::State::update, state->routine->dbgState, dbg->spirvFile->id, line, column);
+	}
 }
 
 void SpirvShader::dbgEndEmitInstruction(InsnIterator insn, EmitState *state) const
@@ -638,7 +1387,7 @@
 	auto dbg = impl.debugger;
 	if(!dbg) { return; }
 
-	dbg->exposeVariable(this, id, id, state);
+	dbg->exposeVariable(this, id, &debug::Scope::Root, nullptr, id, state);
 }
 
 void SpirvShader::dbgUpdateActiveLaneMask(RValue<SIMD::Int> mask, EmitState *state) const
@@ -674,10 +1423,18 @@
 
 void SpirvShader::DefineOpenCLDebugInfo100(const InsnIterator &insn)
 {
+	auto dbg = impl.debugger;
+	if(!dbg) { return; }
+
+	dbg->process(this, 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);
+	}
 	return EmitResult::Continue;
 }