Vulkan/Debug: Overhaul Values / Variables

`Value` had a number of methods that were never used (like `set()`), so just remove them.
Remove `Type`, this was also unused, and was unncessarily complex to maintain.

Add `Variables` interface that allows other composite value implementations that are not `VariableContainer`.

Break the inheritance of `VariableContainer` from `Value`, as this forces pointer casting and doesn't work with the `Variables` interface.

Add `Struct` which is an implementation of `Value` that implements the new `children()` method to return the provided `Variables`.

Sets the groundwork for the overhauled debugger implementation.

Note: The changes to `SpirvShaderDebugger.cpp` are a least-effort set of changes to make things compile.
A significant amount of this file (along with these changes) will be reimplemented in a followup change.

Bug: b/145351270
Change-Id: Ic3a641a246737b1f82c786fa8c1ec75700b2a71a
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/48693
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/Pipeline/SpirvShaderDebugger.cpp b/src/Pipeline/SpirvShaderDebugger.cpp
index 1608bc5..432b937 100644
--- a/src/Pipeline/SpirvShaderDebugger.cpp
+++ b/src/Pipeline/SpirvShaderDebugger.cpp
@@ -453,7 +453,7 @@
 		    vc,
 		    [&](std::shared_ptr<vk::dbg::VariableContainer> &parent, uint32_t idx) {
 			    auto child = std::make_shared<vk::dbg::VariableContainer>();
-			    parent->put(tostring(idx), child);
+			    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) {
@@ -466,7 +466,7 @@
 #	endif
 			    parent->put(key, child);
 		    });
-		return vc;
+		return std::make_shared<vk::dbg::Struct>("array", vc);
 	}
 };
 
@@ -502,7 +502,7 @@
 #	endif
 			vc->put(elKey, base->value(elPtr, interleaved));
 		}
-		return vc;
+		return std::make_shared<vk::dbg::Struct>("vector", vc);
 	}
 };
 
@@ -571,7 +571,7 @@
 #	endif
 			vc->put(elKey, member->type->value(elPtr, interleaved));
 		}
-		return vc;
+		return std::make_shared<vk::dbg::Struct>(name, vc);
 	}
 };
 
@@ -928,7 +928,7 @@
 		for(int i = 0; i < sw::SIMD::Width; i++)
 		{
 			auto locals = std::make_shared<vk::dbg::VariableContainer>();
-			frame.locals->variables->put(laneNames[i], locals);
+			frame.locals->variables->put(laneNames[i], std::make_shared<vk::dbg::Struct>("", locals));
 			globals.localsByLane[i] = locals;
 		}
 	});
@@ -979,7 +979,7 @@
 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), out);
+	vc->put(tostring(key), std::make_shared<vk::dbg::Struct>("", out));
 	return out.get();
 }
 
@@ -1018,7 +1018,7 @@
 	{
 		auto locals = std::make_shared<vk::dbg::VariableContainer>();
 		s.localsByLane[i] = locals;
-		s.locals->variables->put(laneNames[i], locals);
+		s.locals->variables->put(laneNames[i], std::make_shared<vk::dbg::Struct>("", locals));
 	}
 
 	if(hasDebuggerScope(spirvScope->parent))
diff --git a/src/Vulkan/CMakeLists.txt b/src/Vulkan/CMakeLists.txt
index 5128fe9..4652917 100644
--- a/src/Vulkan/CMakeLists.txt
+++ b/src/Vulkan/CMakeLists.txt
@@ -116,8 +116,8 @@
         Debug/Server.hpp
         Debug/Thread.cpp
         Debug/Thread.hpp
-        Debug/Type.cpp
-        Debug/Type.hpp
+        Debug/TypeOf.cpp
+        Debug/TypeOf.hpp
         Debug/Value.cpp
         Debug/Value.hpp
         Debug/Variable.cpp
diff --git a/src/Vulkan/Debug/Context.cpp b/src/Vulkan/Debug/Context.cpp
index ccee98f..b311e2f 100644
--- a/src/Vulkan/Debug/Context.cpp
+++ b/src/Vulkan/Debug/Context.cpp
@@ -183,12 +183,11 @@
 	WeakMap<File::ID, File> files;
 	WeakMap<Frame::ID, Frame> frames;
 	WeakMap<Scope::ID, Scope> scopes;
-	WeakMap<VariableContainer::ID, VariableContainer> variableContainers;
+	WeakMap<Variables::ID, Variables> variables;
 	Thread::ID nextThreadID = 1;
 	File::ID nextFileID = 1;
 	Frame::ID nextFrameID = 1;
 	Scope::ID nextScopeID = 1;
-	VariableContainer::ID nextVariableContainerID = 1;
 };
 
 Context::Lock Context::Impl::lock()
@@ -390,14 +389,14 @@
 	return ctx->scopes.get(id);
 }
 
-void Context::Lock::track(const std::shared_ptr<VariableContainer> &vc)
+void Context::Lock::track(const std::shared_ptr<Variables> &vars)
 {
-	ctx->variableContainers.add(vc->id, vc);
+	ctx->variables.add(vars->id, vars);
 }
 
-std::shared_ptr<VariableContainer> Context::Lock::get(VariableContainer::ID id)
+std::shared_ptr<Variables> Context::Lock::get(Variables::ID id)
 {
-	return ctx->variableContainers.get(id);
+	return ctx->variables.get(id);
 }
 
 void Context::Lock::addFunctionBreakpoint(const std::string &name)
diff --git a/src/Vulkan/Debug/Context.hpp b/src/Vulkan/Debug/Context.hpp
index 832c9c4..64e7e55 100644
--- a/src/Vulkan/Debug/Context.hpp
+++ b/src/Vulkan/Debug/Context.hpp
@@ -29,7 +29,7 @@
 class File;
 class Frame;
 class Scope;
-class VariableContainer;
+class Variables;
 class ClientEventListener;
 class ServerEventListener;
 
@@ -107,16 +107,16 @@
 		// does not exist.
 		std::shared_ptr<Scope> get(ID<Scope>);
 
-		// track() registers the variable container with the context so it can
-		// be retrieved by get(). Note that the context does not hold a strong
-		// reference to the variable container, and get() will return nullptr
-		// if all strong external references are dropped.
-		void track(const std::shared_ptr<VariableContainer> &);
+		// track() registers the variables with the context so it can be
+		// retrieved by get(). Note that the context does not hold a strong
+		// reference to the variables, and get() will return nullptr if all
+		// strong external references are dropped.
+		void track(const std::shared_ptr<Variables> &);
 
-		// get() returns the variable container with the given ID, or null if
-		// the variable container does not exist or no longer has any external
-		// shared_ptr references.
-		std::shared_ptr<VariableContainer> get(ID<VariableContainer>);
+		// get() returns the variables with the given ID, or null if the
+		// variables does not exist or no longer has any external shared_ptr
+		// references.
+		std::shared_ptr<Variables> get(ID<Variables>);
 
 		// addFunctionBreakpoint() adds a breakpoint to the start of the
 		// function with the given name.
diff --git a/src/Vulkan/Debug/Server.cpp b/src/Vulkan/Debug/Server.cpp
index b6d4443..9dd05ce 100644
--- a/src/Vulkan/Debug/Server.cpp
+++ b/src/Vulkan/Debug/Server.cpp
@@ -269,7 +269,7 @@
 		DAP_LOG("VariablesRequest receieved");
 
 		auto lock = ctx->lock();
-		auto vars = lock.get(VariableContainer::ID(req.variablesReference));
+		auto vars = lock.get(Variables::ID(req.variablesReference));
 		if(!vars)
 		{
 			return dap::Error("VariablesReference %d not found",
@@ -281,15 +281,15 @@
 			dap::Variable out;
 			out.evaluateName = v.name;
 			out.name = v.name;
-			out.type = v.value->type()->string();
-			out.value = v.value->string();
-			if(v.value->type()->kind == Kind::VariableContainer)
+			out.type = v.value->type();
+			out.value = v.value->get();
+			if(auto children = v.value->children())
 			{
-				auto const vc = std::dynamic_pointer_cast<VariableContainer>(v.value);
-				out.variablesReference = vc->id.value();
-				lock.track(vc);
+				out.variablesReference = children->id.value();
+				lock.track(children);
 			}
 			response.variables.push_back(out);
+			return true;
 		});
 		return response;
 	});
@@ -446,12 +446,8 @@
 			}
 
 			dap::EvaluateResponse response;
-			auto findHandler = [&](const Variable &var) {
-				response.result = var.value->string(fmt);
-				response.type = var.value->type()->string();
-			};
 
-			std::vector<std::shared_ptr<vk::dbg::VariableContainer>> variables = {
+			std::vector<std::shared_ptr<vk::dbg::Variables>> variables = {
 				frame->locals->variables,
 				frame->arguments->variables,
 				frame->registers->variables,
@@ -460,7 +456,12 @@
 
 			for(auto const &vars : variables)
 			{
-				if(vars->find(req.expression, findHandler)) { return response; }
+				if(auto val = vars->get(req.expression))
+				{
+					response.result = val->get(fmt);
+					response.type = val->type();
+					return response;
+				}
 			}
 
 			// HACK: VSCode does not appear to include the % in %123 tokens
@@ -469,7 +470,12 @@
 			auto withPercent = "%" + req.expression;
 			for(auto const &vars : variables)
 			{
-				if(vars->find(withPercent, findHandler)) { return response; }
+				if(auto val = vars->get(withPercent))
+				{
+					response.result = val->get(fmt);
+					response.type = val->type();
+					return response;
+				}
 			}
 		}
 
diff --git a/src/Vulkan/Debug/Type.cpp b/src/Vulkan/Debug/Type.cpp
deleted file mode 100644
index c3e0ab4..0000000
--- a/src/Vulkan/Debug/Type.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "Type.hpp"
-
-namespace vk {
-namespace dbg {
-
-// clang-format off
-std::shared_ptr<Type> TypeOf<bool>::get()              { static auto ty = std::make_shared<Type>(Kind::Bool); return ty; }
-std::shared_ptr<Type> TypeOf<uint8_t>::get()           { static auto ty = std::make_shared<Type>(Kind::U8);   return ty; }
-std::shared_ptr<Type> TypeOf<int8_t>::get()            { static auto ty = std::make_shared<Type>(Kind::S8);   return ty; }
-std::shared_ptr<Type> TypeOf<uint16_t>::get()          { static auto ty = std::make_shared<Type>(Kind::U16);  return ty; }
-std::shared_ptr<Type> TypeOf<int16_t>::get()           { static auto ty = std::make_shared<Type>(Kind::S16);  return ty; }
-std::shared_ptr<Type> TypeOf<float>::get()             { static auto ty = std::make_shared<Type>(Kind::F32);  return ty; }
-std::shared_ptr<Type> TypeOf<uint32_t>::get()          { static auto ty = std::make_shared<Type>(Kind::U32);  return ty; }
-std::shared_ptr<Type> TypeOf<int32_t>::get()           { static auto ty = std::make_shared<Type>(Kind::S32);  return ty; }
-std::shared_ptr<Type> TypeOf<double>::get()            { static auto ty = std::make_shared<Type>(Kind::F64);  return ty; }
-std::shared_ptr<Type> TypeOf<uint64_t>::get()          { static auto ty = std::make_shared<Type>(Kind::U64);  return ty; }
-std::shared_ptr<Type> TypeOf<int64_t>::get()           { static auto ty = std::make_shared<Type>(Kind::S64);  return ty; }
-std::shared_ptr<Type> TypeOf<VariableContainer>::get() { static auto ty = std::make_shared<Type>(Kind::VariableContainer); return ty; }
-// clang-format on
-
-std::string Type::string() const
-{
-	switch(kind)
-	{
-		case Kind::Bool:
-			return "bool";
-		case Kind::U8:
-			return "uint8_t";
-		case Kind::S8:
-			return "int8_t";
-		case Kind::U16:
-			return "uint16_t";
-		case Kind::S16:
-			return "int16_t";
-		case Kind::F32:
-			return "float";
-		case Kind::U32:
-			return "uint32_t";
-		case Kind::S32:
-			return "int32_t";
-		case Kind::F64:
-			return "double";
-		case Kind::U64:
-			return "uint64_t";
-		case Kind::S64:
-			return "int64_t";
-		case Kind::Ptr:
-			return elem->string() + "*";
-		case Kind::VariableContainer:
-			return "struct";
-	}
-	return "";
-}
-
-}  // namespace dbg
-}  // namespace vk
\ No newline at end of file
diff --git a/src/Vulkan/Debug/Type.hpp b/src/Vulkan/Debug/Type.hpp
deleted file mode 100644
index c3c3d4b..0000000
--- a/src/Vulkan/Debug/Type.hpp
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef VK_DEBUG_TYPE_HPP_
-#define VK_DEBUG_TYPE_HPP_
-
-#include <memory>
-
-#include <cstdint>
-#include <string>
-
-namespace vk {
-namespace dbg {
-
-class VariableContainer;
-class Value;
-
-// Kind is an enumerator of type kinds.
-enum class Kind
-{
-	Bool,               // Boolean
-	U8,                 // 8-bit unsigned integer.
-	S8,                 // 8-bit signed integer.
-	U16,                // 16-bit unsigned integer.
-	S16,                // 16-bit signed integer.
-	F32,                // 32-bit unsigned integer.
-	U32,                // 32-bit signed integer.
-	S32,                // 32-bit unsigned integer.
-	F64,                // 64-bit signed integer.
-	U64,                // 64-bit unsigned integer.
-	S64,                // 64-bit signed integer.
-	Ptr,                // A pointer.
-	VariableContainer,  // A VariableContainer.
-};
-
-// Type describes the type of a value.
-class Type
-{
-public:
-	inline Type(Kind kind);
-	inline Type(Kind kind, const std::shared_ptr<const Type> &elem);
-
-	// string() returns a string representation of the type.
-	std::string string() const;
-
-	const Kind kind;                         // Type kind.
-	const std::shared_ptr<const Type> elem;  // Element type of pointer.
-};
-
-Type::Type(Kind kind)
-    : kind(kind)
-{}
-
-Type::Type(Kind kind, const std::shared_ptr<const Type> &elem)
-    : kind(kind)
-    , elem(elem)
-{}
-
-// clang-format off
-template <typename T> struct TypeOf;
-template <> struct TypeOf<bool>              { static std::shared_ptr<Type> get(); };
-template <> struct TypeOf<uint8_t>           { static std::shared_ptr<Type> get(); };
-template <> struct TypeOf<int8_t>            { static std::shared_ptr<Type> get(); };
-template <> struct TypeOf<uint16_t>          { static std::shared_ptr<Type> get(); };
-template <> struct TypeOf<int16_t>           { static std::shared_ptr<Type> get(); };
-template <> struct TypeOf<float>             { static std::shared_ptr<Type> get(); };
-template <> struct TypeOf<uint32_t>          { static std::shared_ptr<Type> get(); };
-template <> struct TypeOf<int32_t>           { static std::shared_ptr<Type> get(); };
-template <> struct TypeOf<double>            { static std::shared_ptr<Type> get(); };
-template <> struct TypeOf<uint64_t>          { static std::shared_ptr<Type> get(); };
-template <> struct TypeOf<int64_t>           { static std::shared_ptr<Type> get(); };
-template <> struct TypeOf<VariableContainer> { static std::shared_ptr<Type> get(); };
-// clang-format on
-
-}  // namespace dbg
-}  // namespace vk
-
-#endif  // VK_DEBUG_TYPE_HPP_
\ No newline at end of file
diff --git a/src/Vulkan/Debug/TypeOf.cpp b/src/Vulkan/Debug/TypeOf.cpp
new file mode 100644
index 0000000..637d03c
--- /dev/null
+++ b/src/Vulkan/Debug/TypeOf.cpp
@@ -0,0 +1,33 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "TypeOf.hpp"
+
+namespace vk {
+namespace dbg {
+
+std::string TypeOf<bool>::name = "bool";
+std::string TypeOf<uint8_t>::name = "uint8_t";
+std::string TypeOf<int8_t>::name = "int8_t";
+std::string TypeOf<uint16_t>::name = "uint16_t";
+std::string TypeOf<int16_t>::name = "int16_t";
+std::string TypeOf<float>::name = "float";
+std::string TypeOf<uint32_t>::name = "uint32_t";
+std::string TypeOf<int32_t>::name = "int32_t";
+std::string TypeOf<double>::name = "double";
+std::string TypeOf<uint64_t>::name = "uint64_t";
+std::string TypeOf<int64_t>::name = "int64_t";
+
+}  // namespace dbg
+}  // namespace vk
diff --git a/src/Vulkan/Debug/TypeOf.hpp b/src/Vulkan/Debug/TypeOf.hpp
new file mode 100644
index 0000000..3c5a8c5
--- /dev/null
+++ b/src/Vulkan/Debug/TypeOf.hpp
@@ -0,0 +1,44 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef VK_DEBUG_TYPE_HPP_
+#define VK_DEBUG_TYPE_HPP_
+
+#include <memory>
+
+#include <cstdint>
+#include <string>
+
+namespace vk {
+namespace dbg {
+
+// clang-format off
+template <typename T> struct TypeOf;
+template <> struct TypeOf<bool>     { static std::string name; };
+template <> struct TypeOf<uint8_t>  { static std::string name; };
+template <> struct TypeOf<int8_t>   { static std::string name; };
+template <> struct TypeOf<uint16_t> { static std::string name; };
+template <> struct TypeOf<int16_t>  { static std::string name; };
+template <> struct TypeOf<float>    { static std::string name; };
+template <> struct TypeOf<uint32_t> { static std::string name; };
+template <> struct TypeOf<int32_t>  { static std::string name; };
+template <> struct TypeOf<double>   { static std::string name; };
+template <> struct TypeOf<uint64_t> { static std::string name; };
+template <> struct TypeOf<int64_t>  { static std::string name; };
+// clang-format on
+
+}  // namespace dbg
+}  // namespace vk
+
+#endif  // VK_DEBUG_TYPE_HPP_
diff --git a/src/Vulkan/Debug/Value.cpp b/src/Vulkan/Debug/Value.cpp
index 49420b4..8ff221d 100644
--- a/src/Vulkan/Debug/Value.cpp
+++ b/src/Vulkan/Debug/Value.cpp
@@ -13,7 +13,6 @@
 // limitations under the License.
 
 #include "Value.hpp"
-#include "Type.hpp"
 #include "Variable.hpp"
 
 namespace vk {
@@ -27,54 +26,10 @@
 	&FormatFlags::Default,  // subListFmt
 };
 
-std::string Value::string(const FormatFlags &fmt /* = FormatFlags::Default */) const
+std::string Struct::get(const FormatFlags &fmt /* = FormatFlags::Default */)
 {
-	switch(type()->kind)
-	{
-		case Kind::Bool:
-			return *reinterpret_cast<const bool *>(get()) ? "true" : "false";
-		case Kind::U8:
-			return std::to_string(*reinterpret_cast<const uint8_t *>(get()));
-		case Kind::S8:
-			return std::to_string(*reinterpret_cast<const int8_t *>(get()));
-		case Kind::U16:
-			return std::to_string(*reinterpret_cast<const uint16_t *>(get()));
-		case Kind::S16:
-			return std::to_string(*reinterpret_cast<const int16_t *>(get()));
-		case Kind::F32:
-			return std::to_string(*reinterpret_cast<const float *>(get()));
-		case Kind::U32:
-			return std::to_string(*reinterpret_cast<const uint32_t *>(get()));
-		case Kind::S32:
-			return std::to_string(*reinterpret_cast<const int32_t *>(get()));
-		case Kind::F64:
-			return std::to_string(*reinterpret_cast<const double *>(get()));
-		case Kind::U64:
-			return std::to_string(*reinterpret_cast<const uint64_t *>(get()));
-		case Kind::S64:
-			return std::to_string(*reinterpret_cast<const int64_t *>(get()));
-		case Kind::Ptr:
-			return std::to_string(reinterpret_cast<uintptr_t>(get()));
-		case Kind::VariableContainer:
-		{
-			auto const *vc = static_cast<const VariableContainer *>(this);
-			std::string out = "";
-			auto subfmt = *fmt.subListFmt;
-			subfmt.listIndent = fmt.listIndent + fmt.subListFmt->listIndent;
-			bool first = true;
-			vc->foreach(0, ~0, [&](const Variable &var) {
-				if(!first) { out += fmt.listDelimiter; }
-				first = false;
-				out += fmt.listIndent;
-				out += var.name;
-				out += ": ";
-				out += var.value->string(subfmt);
-			});
-			return fmt.listPrefix + out + fmt.listSuffix;
-		}
-	}
-	return "";
+	return members->string(fmt);
 }
 
 }  // namespace dbg
-}  // namespace vk
\ No newline at end of file
+}  // namespace vk
diff --git a/src/Vulkan/Debug/Value.hpp b/src/Vulkan/Debug/Value.hpp
index 15105c2..6deb3d6 100644
--- a/src/Vulkan/Debug/Value.hpp
+++ b/src/Vulkan/Debug/Value.hpp
@@ -15,7 +15,9 @@
 #ifndef VK_DEBUG_VALUE_HPP_
 #define VK_DEBUG_VALUE_HPP_
 
-#include "Type.hpp"
+#include "TypeOf.hpp"
+
+#include "System/Debug.hpp"
 
 #include <memory>
 #include <string>
@@ -23,6 +25,9 @@
 namespace vk {
 namespace dbg {
 
+class Variables;
+class VariableContainer;
+
 // FormatFlags holds settings used to serialize a Value to a string.
 struct FormatFlags
 {
@@ -36,97 +41,85 @@
 	const FormatFlags *subListFmt;  // Format used for list sub items.
 };
 
-// Value holds a value that can be read and possible written to.
+// Value holds a value that can be read.
 class Value
 {
 public:
 	virtual ~Value() = default;
 
-	// type() returns the value's type.
-	virtual std::shared_ptr<Type> type() const = 0;
+	// type() returns the typename for the value.
+	virtual std::string type() = 0;
 
-	// string() returns a string representation of the value using the specified
+	// get() returns a string representation of the value using the specified
 	// FormatFlags.
-	virtual std::string string(const FormatFlags & = FormatFlags::Default) const;
+	virtual std::string get(const FormatFlags & = FormatFlags::Default) = 0;
 
-	// get() returns a pointer to the value.
-	virtual const void *get() const = 0;
-
-	// set() changes the value to a copy of the value at ptr.
-	// set() returns true if the value was changed, or false if the value cannot
-	// be set.
-	virtual bool set(void *ptr) { return false; }
+	// children() returns the optional child members of this value.
+	virtual std::shared_ptr<Variables> children() { return nullptr; }
 };
 
-// Constant is an immutable value.
+// Constant is constant value of type T.
 template<typename T>
 class Constant : public Value
 {
 public:
-	inline Constant(const T &value);
-	inline std::shared_ptr<Type> type() const override;
-	inline const void *get() const override;
+	Constant(const T &val)
+	    : val(val)
+	{}
+	std::string type() override { return TypeOf<T>::name; }
+	std::string get(const FormatFlags &fmt = FormatFlags::Default) override { return std::to_string(val); }
 
 private:
-	const T value;
+	T const val;
 };
 
-template<typename T>
-Constant<T>::Constant(const T &value)
-    : value(value)
-{
-}
-
-template<typename T>
-std::shared_ptr<Type> Constant<T>::type() const
-{
-	return TypeOf<T>::get();
-}
-
-template<typename T>
-const void *Constant<T>::get() const
-{
-	return &value;
-}
-
 // Reference is reference to a value in memory.
 template<typename T>
 class Reference : public Value
 {
 public:
-	inline Reference(T &ptr);
-	inline std::shared_ptr<Type> type() const override;
-	inline const void *get() const override;
-	inline bool set(void *ptr) override;
+	Reference(const T &ref)
+	    : ref(ref)
+	{}
+	std::string type() override { return TypeOf<T>::name; }
+	std::string get(const FormatFlags &fmt = FormatFlags::Default) override { return std::to_string(ref); }
 
 private:
-	T &ref;
+	T const &ref;
 };
 
-template<typename T>
-Reference<T>::Reference(T &ref)
-    : ref(ref)
+// Struct is an implementation of Value that delegates calls to children() on to
+// the constructor provided Variables.
+class Struct : public Value
 {
-}
+public:
+	Struct(const std::string &type, const std::shared_ptr<Variables> &members)
+	    : ty(type)
+	    , members(members)
+	{
+		ASSERT(members);
+	}
 
-template<typename T>
-std::shared_ptr<Type> Reference<T>::type() const
-{
-	return TypeOf<T>::get();
-}
+	std::string type() override { return ty; }
+	std::string get(const FormatFlags &fmt = FormatFlags::Default) override;
+	std::shared_ptr<Variables> children() override { return members; }
 
-template<typename T>
-const void *Reference<T>::get() const
-{
-	return &ref;
-}
+	// create() constructs and returns a new Struct with the given type name and
+	// calls fields to populate the child members.
+	// fields must be a function that has the signature:
+	//   void(std::shared_pointer<VariableContainer>&)
+	template<typename F>
+	static std::shared_ptr<Struct> create(const std::string &name, F &&fields)
+	{
+		auto vc = std::make_shared<VariableContainer>();
+		fields(vc);
+		return std::make_shared<Struct>(name, vc);
+	}
 
-template<typename T>
-bool Reference<T>::set(void *ptr)
-{
-	ref = *reinterpret_cast<const T *>(ptr);
-	return true;
-}
+private:
+	std::string const ty;
+	std::shared_ptr<Variables> const members;
+};
 
 // make_constant() returns a shared_ptr to a Constant with the given value.
 template<typename T>
@@ -137,7 +130,7 @@
 
 // make_reference() returns a shared_ptr to a Reference with the given value.
 template<typename T>
-inline std::shared_ptr<Reference<T>> make_reference(T &value)
+inline std::shared_ptr<Reference<T>> make_reference(const T &value)
 {
 	return std::make_shared<Reference<T>>(value);
 }
@@ -145,4 +138,4 @@
 }  // namespace dbg
 }  // namespace vk
 
-#endif  // VK_DEBUG_VALUE_HPP_
\ No newline at end of file
+#endif  // VK_DEBUG_VALUE_HPP_
diff --git a/src/Vulkan/Debug/Variable.cpp b/src/Vulkan/Debug/Variable.cpp
index 8145e01..abade64 100644
--- a/src/Vulkan/Debug/Variable.cpp
+++ b/src/Vulkan/Debug/Variable.cpp
@@ -17,7 +17,41 @@
 namespace vk {
 namespace dbg {
 
-std::atomic<int> VariableContainer::nextID{};
+std::atomic<int> Variables::nextID{};
+
+Variables::~Variables() = default;
+
+std::shared_ptr<Value> Variables::get(const std::string &name)
+{
+	std::shared_ptr<Value> found;
+	foreach([&](const Variable &var) {
+		if(var.name == name)
+		{
+			found = var.value;
+			return false;
+		}
+		return true;
+	});
+	return found;
+}
+
+std::string Variables::string(const FormatFlags &fmt /* = FormatFlags::Default */)
+{
+	std::string out = "";
+	auto subfmt = *fmt.subListFmt;
+	subfmt.listIndent = fmt.listIndent + fmt.subListFmt->listIndent;
+	bool first = true;
+	foreach([&](const Variable &var) {
+		if(!first) { out += fmt.listDelimiter; }
+		first = false;
+		out += fmt.listIndent;
+		out += var.name;
+		out += ": ";
+		out += var.value->get(subfmt);
+		return true;
+	});
+	return fmt.listPrefix + out + fmt.listSuffix;
+}
 
 }  // namespace dbg
 }  // namespace vk
diff --git a/src/Vulkan/Debug/Variable.hpp b/src/Vulkan/Debug/Variable.hpp
index 8818b5b..cbc8d51 100644
--- a/src/Vulkan/Debug/Variable.hpp
+++ b/src/Vulkan/Debug/Variable.hpp
@@ -16,14 +16,16 @@
 #define VK_DEBUG_VARIABLE_HPP_
 
 #include "ID.hpp"
-#include "Type.hpp"
 #include "Value.hpp"
 
+#include "System/Debug.hpp"
+
 #include "marl/mutex.h"
 #include "marl/tsa.h"
 
 #include <algorithm>
 #include <atomic>
+#include <limits>
 #include <memory>
 #include <string>
 #include <unordered_map>
@@ -37,26 +39,64 @@
 {
 	std::string name;
 	std::shared_ptr<Value> value;
+
+	// operator bool returns true iff value is not nullptr.
+	operator bool() const { return value != nullptr; }
 };
 
-// VariableContainer is a collection of named values.
-class VariableContainer : public Value
+// Variables is an interface to a collection of named values.
+class Variables
+{
+public:
+	using ID = dbg::ID<Variables>;
+
+	using ForeachCallback = std::function<bool(const Variable &)>;
+	using FindCallback = std::function<void(const Variable &)>;
+
+	inline Variables();
+	virtual ~Variables();
+
+	// foreach() calls cb with each of the variables in the container, while cb
+	// returns true.
+	// foreach() will return when cb returns false.
+	inline void foreach(const ForeachCallback &cb);
+
+	// foreach() calls cb with each of the variables in the container within the
+	// indexed range: [startIndex, startIndex+count), while cb returns true.
+	// foreach() will return when cb returns false.
+	virtual void foreach(size_t startIndex, size_t count, const ForeachCallback &cb) = 0;
+
+	// get() looks up and returns the variable with the given name.
+	virtual std::shared_ptr<Value> get(const std::string &name);
+
+	// string() returns the list of variables formatted to a string using the
+	// given flags.
+	virtual std::string string(const FormatFlags &fmt /* = FormatFlags::Default */);
+
+	// The unique identifier of the variables.
+	const ID id;
+
+private:
+	static std::atomic<int> nextID;
+};
+
+Variables::Variables()
+    : id(nextID++)
+{}
+
+void Variables::foreach(const ForeachCallback &cb)
+{
+	foreach(0, std::numeric_limits<size_t>::max(), cb);
+}
+
+// VariableContainer is mutable collection of named values.
+class VariableContainer : public Variables
 {
 public:
 	using ID = dbg::ID<VariableContainer>;
 
-	inline VariableContainer();
-
-	// foreach() calls cb with each of the variables in the container.
-	// F must be a function with the signature void(const Variable&).
-	template<typename F>
-	inline void foreach(size_t startIndex, size_t count, const F &cb) const;
-
-	// find() looks up the variable with the given name.
-	// If the variable with the given name is found, cb is called with the
-	// variable and find() returns true.
-	template<typename F>
-	inline bool find(const std::string &name, const F &cb) const;
+	inline void foreach(size_t startIndex, size_t count, const ForeachCallback &cb) override;
+	inline std::shared_ptr<Value> get(const std::string &name) override;
 
 	// put() places the variable var into the container.
 	inline void put(const Variable &var);
@@ -64,16 +104,12 @@
 	// put() places the variable with the given name and value into the container.
 	inline void put(const std::string &name, const std::shared_ptr<Value> &value);
 
-	// extend() adds base to the list of VariableContainers that will be
-	// searched and traversed for variables.
-	inline void extend(const std::shared_ptr<VariableContainer> &base);
-
-	// The unique identifier of the variable.
-	const ID id;
+	// extend() adds base to the list of Variables that will be searched and
+	// traversed for variables after those in this VariableContainer are
+	// searched / traversed.
+	inline void extend(const std::shared_ptr<Variables> &base);
 
 private:
-	static std::atomic<int> nextID;
-
 	struct ForeachIndex
 	{
 		size_t start;
@@ -81,35 +117,30 @@
 	};
 
 	template<typename F>
-	inline void foreach(ForeachIndex &index, const F &cb) const;
-
-	inline std::shared_ptr<Type> type() const override;
-	inline const void *get() const override;
+	inline void foreach(ForeachIndex &index, const F &cb);
 
 	mutable marl::mutex mutex;
 	std::vector<Variable> variables GUARDED_BY(mutex);
 	std::unordered_map<std::string, int> indices GUARDED_BY(mutex);
-	std::vector<std::shared_ptr<VariableContainer>> extends GUARDED_BY(mutex);
+	std::vector<std::shared_ptr<Variables>> extends GUARDED_BY(mutex);
 };
 
-VariableContainer::VariableContainer()
-    : id(nextID++)
-{}
-
-template<typename F>
-void VariableContainer::foreach(size_t startIndex, size_t count, const F &cb) const
+void VariableContainer::foreach(size_t startIndex, size_t count, const ForeachCallback &cb)
 {
 	auto index = ForeachIndex{ startIndex, count };
 	foreach(index, cb);
 }
 
 template<typename F>
-void VariableContainer::foreach(ForeachIndex &index, const F &cb) const
+void VariableContainer::foreach(ForeachIndex &index, const F &cb)
 {
 	marl::lock lock(mutex);
 	for(size_t i = index.start; i < variables.size() && i < index.count; i++)
 	{
-		cb(variables[i]);
+		if(!cb(variables[i]))
+		{
+			return;
+		}
 	}
 
 	index.start -= std::min(index.start, variables.size());
@@ -117,34 +148,34 @@
 
 	for(auto &base : extends)
 	{
-		base->foreach(index, cb);
+		base->foreach(index.start, index.count, cb);
 	}
 }
 
-template<typename F>
-bool VariableContainer::find(const std::string &name, const F &cb) const
+std::shared_ptr<Value> VariableContainer::get(const std::string &name)
 {
 	marl::lock lock(mutex);
 	for(auto const &var : variables)
 	{
 		if(var.name == name)
 		{
-			cb(var);
-			return true;
+			return var.value;
 		}
 	}
 	for(auto &base : extends)
 	{
-		if(base->find(name, cb))
+		if(auto val = base->get(name))
 		{
-			return true;
+			return val;
 		}
 	}
-	return false;
+	return nullptr;
 }
 
 void VariableContainer::put(const Variable &var)
 {
+	ASSERT(var.value);
+
 	marl::lock lock(mutex);
 	auto it = indices.find(var.name);
 	if(it == indices.end())
@@ -164,22 +195,12 @@
 	put({ name, value });
 }
 
-void VariableContainer::extend(const std::shared_ptr<VariableContainer> &base)
+void VariableContainer::extend(const std::shared_ptr<Variables> &base)
 {
 	marl::lock lock(mutex);
 	extends.emplace_back(base);
 }
 
-std::shared_ptr<Type> VariableContainer::type() const
-{
-	return TypeOf<VariableContainer>::get();
-}
-
-const void *VariableContainer::get() const
-{
-	return nullptr;
-}
-
 }  // namespace dbg
 }  // namespace vk