SpirvShaderDebugger: Reduce lock contention

Previously the way to create `VariableContainer`s was to call
`createVariableContainer()` on the `Context::Lock`. This was required so that debug-client requests to inspect the container's contents can be done with a simple map lookup.
Creating variable containers is a high frequency operation, and obtaining a `Context::Lock` (unsurprisingly) requires acquiring a mutex lock, which across multiple threads results in significant mutex lock contention.

To dramatically help with debugger performance, we can now construct `VariableContainer`s outside of the lock. This is achieved by registering the variable container in the context map only when it is handed to the client. This only occurs when there's a DAP transaction, which is orders of magnitude less frequent.

Bug: b/148401179
Change-Id: I58eca33aac8fd4711dd9756072a6a06efdacc671
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/48428
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jaebaek Seo <jaebaek@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
diff --git a/src/Pipeline/SpirvShaderDebugger.cpp b/src/Pipeline/SpirvShaderDebugger.cpp
index 28993f7..06f75c9 100644
--- a/src/Pipeline/SpirvShaderDebugger.cpp
+++ b/src/Pipeline/SpirvShaderDebugger.cpp
@@ -261,7 +261,7 @@
 
 	// value() returns a shared pointer to a vk::dbg::Value that views the data
 	// at ptr of this type.
-	virtual std::shared_ptr<vk::dbg::Value> value(vk::dbg::Context::Lock &lock, void *ptr, bool interleaved) const = 0;
+	virtual std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const = 0;
 };
 
 struct CompilationUnit : ObjectImpl<CompilationUnit, Scope, Object::Kind::CompilationUnit>
@@ -286,7 +286,7 @@
 
 	uint32_t sizeInBytes() const override { return size / 8; }
 
-	std::shared_ptr<vk::dbg::Value> value(vk::dbg::Context::Lock &lock, void *ptr, bool interleaved) const override
+	std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
 	{
 		switch(encoding)
 		{
@@ -397,20 +397,20 @@
 		return numBytes;
 	}
 
-	std::shared_ptr<vk::dbg::Value> value(vk::dbg::Context::Lock &lock, void *ptr, bool interleaved) const override
+	std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
 	{
-		auto vc = lock.createVariableContainer();
+		auto vc = std::make_shared<vk::dbg::VariableContainer>();
 		build(
 		    vc,
 		    [&](std::shared_ptr<vk::dbg::VariableContainer> &parent, uint32_t idx) {
-			    auto child = lock.createVariableContainer();
+			    auto child = std::make_shared<vk::dbg::VariableContainer>();
 			    parent->put(tostring(idx), 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(lock, addr, interleaved);
+			    auto child = base->value(addr, interleaved);
 			    auto key = tostring(idx);
 #	if DEBUG_ANNOTATE_VARIABLE_KEYS
 			    key += " (" + tostring(addr) + " +" + tostring(offset) + ", idx: " + tostring(idx) + ")" + (interleaved ? "I" : "F");
@@ -431,10 +431,10 @@
 		return base->sizeInBytes() * components;
 	}
 
-	std::shared_ptr<vk::dbg::Value> value(vk::dbg::Context::Lock &lock, void *ptr, bool interleaved) const override
+	std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
 	{
 		const auto elSize = base->sizeInBytes();
-		auto vc = lock.createVariableContainer();
+		auto vc = std::make_shared<vk::dbg::VariableContainer>();
 		for(uint32_t i = 0; i < components; i++)
 		{
 			auto offset = elSize * i * (interleaved ? sw::SIMD::Width : 1);
@@ -443,7 +443,7 @@
 #	if DEBUG_ANNOTATE_VARIABLE_KEYS
 			elKey += " (" + tostring(elPtr) + " +" + tostring(offset) + ")" + (interleaved ? "I" : "F");
 #	endif
-			vc->put(elKey, base->value(lock, elPtr, interleaved));
+			vc->put(elKey, base->value(elPtr, interleaved));
 		}
 		return vc;
 	}
@@ -456,7 +456,7 @@
 	std::vector<Type *> paramTys;
 
 	uint32_t sizeInBytes() const override { return 0; }
-	std::shared_ptr<vk::dbg::Value> value(vk::dbg::Context::Lock &lock, void *ptr, bool interleaved) const override { return nullptr; }
+	std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override { return nullptr; }
 };
 
 struct Member : ObjectImpl<Member, Object, Object::Kind::Member>
@@ -486,9 +486,10 @@
 	std::vector<Member *> members;
 
 	uint32_t sizeInBytes() const override { return size / 8; }
-	std::shared_ptr<vk::dbg::Value> value(vk::dbg::Context::Lock &lock, void *ptr, bool interleaved) const override
+
+	std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
 	{
-		auto vc = lock.createVariableContainer();
+		auto vc = std::make_shared<vk::dbg::VariableContainer>();
 		for(auto &member : members)
 		{
 			auto offset = (member->offset / 8) * (interleaved ? sw::SIMD::Width : 1);
@@ -497,7 +498,7 @@
 #	if DEBUG_ANNOTATE_VARIABLE_KEYS
 			// elKey += " (" + tostring(elPtr) + " +" + tostring(offset) + ")" + (interleaved ? "I" : "F");
 #	endif
-			vc->put(elKey, member->type->value(lock, elPtr, interleaved));
+			vc->put(elKey, member->type->value(elPtr, interleaved));
 		}
 		return vc;
 	}
@@ -519,9 +520,9 @@
 	std::vector<TemplateParameter *> parameters;
 
 	uint32_t sizeInBytes() const override { return target->sizeInBytes(); }
-	std::shared_ptr<vk::dbg::Value> value(vk::dbg::Context::Lock &lock, void *ptr, bool interleaved) const override
+	std::shared_ptr<vk::dbg::Value> value(void *ptr, bool interleaved) const override
 	{
-		return target->value(lock, ptr, interleaved);
+		return target->value(ptr, interleaved);
 	}
 };
 
@@ -829,7 +830,7 @@
 		globals.hovers = frame.hovers;
 		for(int i = 0; i < sw::SIMD::Width; i++)
 		{
-			auto locals = lock.createVariableContainer();
+			auto locals = std::make_shared<vk::dbg::VariableContainer>();
 			frame.locals->variables->put(laneNames[i], locals);
 			globals.localsByLane[i] = locals;
 		}
@@ -880,7 +881,7 @@
 template<typename K>
 vk::dbg::VariableContainer *SpirvShader::Impl::Debugger::State::group(vk::dbg::VariableContainer *vc, K key)
 {
-	auto out = debugger->ctx->lock().createVariableContainer();
+	auto out = std::make_shared<vk::dbg::VariableContainer>();
 	vc->put(tostring(key), out);
 	return out.get();
 }
@@ -894,8 +895,7 @@
 template<typename K>
 void SpirvShader::Impl::Debugger::State::putPtr(vk::dbg::VariableContainer *vc, K key, void *ptr, bool interleaved, const debug::Type *type)
 {
-	auto lock = debugger->ctx->lock();
-	vc->put(tostring(key), type->value(lock, ptr, interleaved));
+	vc->put(tostring(key), type->value(ptr, interleaved));
 }
 
 template<typename K, typename V>
@@ -919,7 +919,7 @@
 
 	for(int i = 0; i < sw::SIMD::Width; i++)
 	{
-		auto locals = lock.createVariableContainer();
+		auto locals = std::make_shared<vk::dbg::VariableContainer>();
 		s.localsByLane[i] = locals;
 		s.locals->variables->put(laneNames[i], locals);
 	}
diff --git a/src/Vulkan/CMakeLists.txt b/src/Vulkan/CMakeLists.txt
index bbd53cd..58ca289 100644
--- a/src/Vulkan/CMakeLists.txt
+++ b/src/Vulkan/CMakeLists.txt
@@ -119,6 +119,7 @@
         Debug/Type.hpp
         Debug/Value.cpp
         Debug/Value.hpp
+        Debug/Variable.cpp
         Debug/Variable.hpp
         Debug/WeakMap.hpp
     )
diff --git a/src/Vulkan/Debug/Context.cpp b/src/Vulkan/Debug/Context.cpp
index f79d345..eb43b4a 100644
--- a/src/Vulkan/Debug/Context.cpp
+++ b/src/Vulkan/Debug/Context.cpp
@@ -311,7 +311,7 @@
 std::shared_ptr<Scope> Context::Lock::createScope(
     const std::shared_ptr<File> &file)
 {
-	auto scope = std::make_shared<Scope>(ctx->nextScopeID++, file, createVariableContainer());
+	auto scope = std::make_shared<Scope>(ctx->nextScopeID++, file, std::make_shared<VariableContainer>());
 	ctx->scopes.add(scope->id, scope);
 	return scope;
 }
@@ -321,11 +321,9 @@
 	return ctx->scopes.get(id);
 }
 
-std::shared_ptr<VariableContainer> Context::Lock::createVariableContainer()
+void Context::Lock::track(const std::shared_ptr<VariableContainer> &vc)
 {
-	auto container = std::make_shared<VariableContainer>(ctx->nextVariableContainerID++);
-	ctx->variableContainers.add(container->id, container);
-	return container;
+	ctx->variableContainers.add(vc->id, vc);
 }
 
 std::shared_ptr<VariableContainer> Context::Lock::get(VariableContainer::ID id)
@@ -349,4 +347,4 @@
 }
 
 }  // namespace dbg
-}  // namespace vk
\ No newline at end of file
+}  // namespace vk
diff --git a/src/Vulkan/Debug/Context.hpp b/src/Vulkan/Debug/Context.hpp
index 4c4d8af..92f3086 100644
--- a/src/Vulkan/Debug/Context.hpp
+++ b/src/Vulkan/Debug/Context.hpp
@@ -106,8 +106,11 @@
 		// does not exist.
 		std::shared_ptr<Scope> get(ID<Scope>);
 
-		// createVariableContainer() returns a new variable container.
-		std::shared_ptr<VariableContainer> createVariableContainer();
+		// 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> &);
 
 		// get() returns the variable container with the given ID, or null if
 		// the variable container does not exist or no longer has any external
diff --git a/src/Vulkan/Debug/Server.cpp b/src/Vulkan/Debug/Server.cpp
index 667caee..6b3b692 100644
--- a/src/Vulkan/Debug/Server.cpp
+++ b/src/Vulkan/Debug/Server.cpp
@@ -55,7 +55,7 @@
 	void onLineBreakpointHit(ID<Thread>) override;
 	void onFunctionBreakpointHit(ID<Thread>) override;
 
-	dap::Scope scope(const char *type, Scope *);
+	dap::Scope scope(Context::Lock &lock, const char *type, Scope *);
 	dap::Source source(File *);
 	std::shared_ptr<File> file(const dap::Source &source);
 
@@ -230,9 +230,9 @@
 
 		dap::ScopesResponse response;
 		response.scopes = {
-			scope("locals", frame->locals.get()),
-			scope("arguments", frame->arguments.get()),
-			scope("registers", frame->registers.get()),
+			scope(lock, "locals", frame->locals.get()),
+			scope(lock, "arguments", frame->arguments.get()),
+			scope(lock, "registers", frame->registers.get()),
 		};
 		return response;
 	});
@@ -258,8 +258,9 @@
 			out.value = v.value->string();
 			if(v.value->type()->kind == Kind::VariableContainer)
 			{
-				auto const vc = static_cast<const VariableContainer *>(v.value.get());
+				auto const vc = std::dynamic_pointer_cast<VariableContainer>(v.value);
 				out.variablesReference = vc->id.value();
+				lock.track(vc);
 			}
 			response.variables.push_back(out);
 		});
@@ -512,7 +513,7 @@
 	session->send(event);
 }
 
-dap::Scope Server::Impl::scope(const char *type, Scope *s)
+dap::Scope Server::Impl::scope(Context::Lock &lock, const char *type, Scope *s)
 {
 	dap::Scope out;
 	// out.line = s->startLine;
@@ -521,6 +522,7 @@
 	out.name = type;
 	out.presentationHint = type;
 	out.variablesReference = s->variables->id.value();
+	lock.track(s->variables);
 	return out;
 }
 
@@ -588,4 +590,4 @@
 }
 
 }  // namespace dbg
-}  // namespace vk
\ No newline at end of file
+}  // namespace vk
diff --git a/src/Vulkan/Debug/Variable.cpp b/src/Vulkan/Debug/Variable.cpp
new file mode 100644
index 0000000..8145e01
--- /dev/null
+++ b/src/Vulkan/Debug/Variable.cpp
@@ -0,0 +1,23 @@
+// 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 "Variable.hpp"
+
+namespace vk {
+namespace dbg {
+
+std::atomic<int> VariableContainer::nextID{};
+
+}  // namespace dbg
+}  // namespace vk
diff --git a/src/Vulkan/Debug/Variable.hpp b/src/Vulkan/Debug/Variable.hpp
index c916d75..8818b5b 100644
--- a/src/Vulkan/Debug/Variable.hpp
+++ b/src/Vulkan/Debug/Variable.hpp
@@ -23,6 +23,7 @@
 #include "marl/tsa.h"
 
 #include <algorithm>
+#include <atomic>
 #include <memory>
 #include <string>
 #include <unordered_map>
@@ -44,7 +45,7 @@
 public:
 	using ID = dbg::ID<VariableContainer>;
 
-	inline VariableContainer(ID id);
+	inline VariableContainer();
 
 	// foreach() calls cb with each of the variables in the container.
 	// F must be a function with the signature void(const Variable&).
@@ -71,6 +72,8 @@
 	const ID id;
 
 private:
+	static std::atomic<int> nextID;
+
 	struct ForeachIndex
 	{
 		size_t start;
@@ -89,8 +92,8 @@
 	std::vector<std::shared_ptr<VariableContainer>> extends GUARDED_BY(mutex);
 };
 
-VariableContainer::VariableContainer(ID id)
-    : id(id)
+VariableContainer::VariableContainer()
+    : id(nextID++)
 {}
 
 template<typename F>
@@ -180,4 +183,4 @@
 }  // namespace dbg
 }  // namespace vk
 
-#endif  // VK_DEBUG_VARIABLE_HPP_
\ No newline at end of file
+#endif  // VK_DEBUG_VARIABLE_HPP_