Vulkan/Debug: Add Context

Holds the global debugger state.

Bug: b/145351270
Change-Id: I4e483c145af068755716f23c09adcc68d138ceaa
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/38898
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
diff --git a/src/Vulkan/Debug/Context.cpp b/src/Vulkan/Debug/Context.cpp
new file mode 100644
index 0000000..ce844eb
--- /dev/null
+++ b/src/Vulkan/Debug/Context.cpp
@@ -0,0 +1,356 @@
+// 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 "Context.hpp"
+
+#include "EventListener.hpp"
+#include "File.hpp"
+#include "Thread.hpp"
+#include "Variable.hpp"
+#include "WeakMap.hpp"
+
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace
+{
+
+class Broadcaster : public vk::dbg::EventListener
+{
+public:
+	using Thread = vk::dbg::Thread;
+
+	// EventListener
+	void onThreadStarted(Thread::ID) override;
+	void onThreadStepped(Thread::ID) override;
+	void onLineBreakpointHit(Thread::ID) override;
+	void onFunctionBreakpointHit(Thread::ID) override;
+
+	void add(EventListener*);
+	void remove(EventListener*);
+
+private:
+	template <typename F>
+	inline void foreach(F&&);
+
+	template <typename F>
+	inline void modify(F&&);
+
+	using ListenerSet = std::unordered_set<EventListener*>;
+	std::recursive_mutex mutex;
+	std::shared_ptr<ListenerSet> listeners = std::make_shared<ListenerSet>();
+	int listenersInUse = 0;
+};
+
+void Broadcaster::onThreadStarted(Thread::ID id)
+{
+	foreach([&](EventListener* l) { l->onThreadStarted(id); });
+}
+
+void Broadcaster::onThreadStepped(Thread::ID id)
+{
+	foreach([&](EventListener* l) { l->onThreadStepped(id); });
+}
+
+void Broadcaster::onLineBreakpointHit(Thread::ID id)
+{
+	foreach([&](EventListener* l) { l->onLineBreakpointHit(id); });
+}
+
+void Broadcaster::onFunctionBreakpointHit(Thread::ID id)
+{
+	foreach([&](EventListener* l) { l->onFunctionBreakpointHit(id); });
+}
+
+void Broadcaster::add(EventListener* l)
+{
+	modify([&]() { listeners->emplace(l); });
+}
+
+void Broadcaster::remove(EventListener* l)
+{
+	modify([&]() { listeners->erase(l); });
+}
+
+template <typename F>
+void Broadcaster::foreach(F&& f)
+{
+	std::unique_lock<std::recursive_mutex> lock(mutex);
+	++listenersInUse;
+	auto copy = listeners;
+	for(auto l : *copy) { f(l); }
+	--listenersInUse;
+}
+
+template <typename F>
+void Broadcaster::modify(F&& f)
+{
+	std::unique_lock<std::recursive_mutex> lock(mutex);
+	if (listenersInUse > 0)
+	{
+		// The listeners map is current being iterated over.
+		// Make a copy before making the edit.
+		listeners = std::make_shared<ListenerSet>(*listeners);
+	}
+	f();
+}
+
+}  // namespace
+
+namespace vk
+{
+namespace dbg
+{
+
+////////////////////////////////////////////////////////////////////////////////
+// Context::Impl
+////////////////////////////////////////////////////////////////////////////////
+class Context::Impl : public Context
+{
+public:
+	// Context compliance
+	Lock lock() override;
+	void addListener(EventListener*) override;
+	void removeListener(EventListener*) override;
+	EventListener* broadcast() override;
+
+	void addFile(const std::shared_ptr<File>& file);
+
+	Broadcaster broadcaster;
+
+	std::mutex mutex;
+	std::vector<EventListener*> eventListeners;
+	std::unordered_map<std::thread::id, std::shared_ptr<Thread>> threadsByStdId;
+	std::unordered_set<std::string> functionBreakpoints;
+	std::unordered_map<std::string, std::vector<int>> pendingBreakpoints;
+	WeakMap<Thread::ID, Thread> threads;
+	WeakMap<File::ID, File> files;
+	WeakMap<Frame::ID, Frame> frames;
+	WeakMap<Scope::ID, Scope> scopes;
+	WeakMap<VariableContainer::ID, VariableContainer> variableContainers;
+	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()
+{
+	return Lock(this);
+}
+
+void Context::Impl::addListener(EventListener* l)
+{
+	broadcaster.add(l);
+}
+
+void Context::Impl::removeListener(EventListener* l)
+{
+	broadcaster.remove(l);
+}
+
+EventListener* Context::Impl::broadcast()
+{
+	return &broadcaster;
+}
+
+void Context::Impl::addFile(const std::shared_ptr<File>& file)
+{
+	files.add(file->id, file);
+
+	auto it = pendingBreakpoints.find(file->name);
+	if(it != pendingBreakpoints.end())
+	{
+		for(auto line : it->second)
+		{
+			file->addBreakpoint(line);
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Context
+////////////////////////////////////////////////////////////////////////////////
+std::shared_ptr<Context> Context::create()
+{
+	return std::shared_ptr<Context>(new Context::Impl());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Context::Lock
+////////////////////////////////////////////////////////////////////////////////
+Context::Lock::Lock(Impl* ctx) :
+    ctx(ctx)
+{
+	ctx->mutex.lock();
+}
+
+Context::Lock::Lock(Lock&& o) :
+    ctx(o.ctx)
+{
+	o.ctx = nullptr;
+}
+
+Context::Lock::~Lock()
+{
+	unlock();
+}
+
+Context::Lock& Context::Lock::operator=(Lock&& o)
+{
+	ctx = o.ctx;
+	o.ctx = nullptr;
+	return *this;
+}
+
+void Context::Lock::unlock()
+{
+	if(ctx)
+	{
+		ctx->mutex.unlock();
+		ctx = nullptr;
+	}
+}
+
+std::shared_ptr<Thread> Context::Lock::currentThread()
+{
+	auto threadIt = ctx->threadsByStdId.find(std::this_thread::get_id());
+	if(threadIt != ctx->threadsByStdId.end())
+	{
+		return threadIt->second;
+	}
+	auto id = ++ctx->nextThreadID;
+	char name[256];
+	snprintf(name, sizeof(name), "Thread<0x%x>", id.value());
+
+	auto thread = std::make_shared<Thread>(id, ctx);
+	ctx->threads.add(id, thread);
+	thread->setName(name);
+	ctx->threadsByStdId.emplace(std::this_thread::get_id(), thread);
+
+	ctx->broadcast()->onThreadStarted(id);
+
+	return thread;
+}
+
+std::shared_ptr<Thread> Context::Lock::get(Thread::ID id)
+{
+	return ctx->threads.get(id);
+}
+
+std::vector<std::shared_ptr<Thread>> Context::Lock::threads()
+{
+	std::vector<std::shared_ptr<Thread>> out;
+	out.reserve(ctx->threads.approx_size());
+	for(auto it : ctx->threads)
+	{
+		out.push_back(it.second);
+	}
+	return out;
+}
+
+std::shared_ptr<File> Context::Lock::createVirtualFile(const std::string& name,
+                                                       const std::string& source)
+{
+	auto file = File::createVirtual(ctx->nextFileID++, name, source);
+	ctx->addFile(file);
+	return file;
+}
+
+std::shared_ptr<File> Context::Lock::createPhysicalFile(const std::string& path)
+{
+	auto file = File::createPhysical(ctx->nextFileID++, path);
+	ctx->addFile(file);
+	return file;
+}
+
+std::shared_ptr<File> Context::Lock::get(File::ID id)
+{
+	return ctx->files.get(id);
+}
+
+std::vector<std::shared_ptr<File>> Context::Lock::files()
+{
+	std::vector<std::shared_ptr<File>> out;
+	out.reserve(ctx->files.approx_size());
+	for(auto it : ctx->files)
+	{
+		out.push_back(it.second);
+	}
+	return out;
+}
+
+std::shared_ptr<Frame> Context::Lock::createFrame(
+    const std::shared_ptr<File>& file)
+{
+	auto frame = std::make_shared<Frame>(ctx->nextFrameID++);
+	ctx->frames.add(frame->id, frame);
+	frame->arguments = createScope(file);
+	frame->locals = createScope(file);
+	frame->registers = createScope(file);
+	frame->hovers = createScope(file);
+	return frame;
+}
+
+std::shared_ptr<Frame> Context::Lock::get(Frame::ID id)
+{
+	return ctx->frames.get(id);
+}
+
+std::shared_ptr<Scope> Context::Lock::createScope(
+    const std::shared_ptr<File>& file)
+{
+	auto scope = std::make_shared<Scope>(ctx->nextScopeID++, file, createVariableContainer());
+	ctx->scopes.add(scope->id, scope);
+	return scope;
+}
+
+std::shared_ptr<Scope> Context::Lock::get(Scope::ID id)
+{
+	return ctx->scopes.get(id);
+}
+
+std::shared_ptr<VariableContainer> Context::Lock::createVariableContainer()
+{
+	auto container = std::make_shared<VariableContainer>(ctx->nextVariableContainerID++);
+	ctx->variableContainers.add(container->id, container);
+	return container;
+}
+
+std::shared_ptr<VariableContainer> Context::Lock::get(VariableContainer::ID id)
+{
+	return ctx->variableContainers.get(id);
+}
+
+void Context::Lock::addFunctionBreakpoint(const std::string& name)
+{
+	ctx->functionBreakpoints.emplace(name);
+}
+
+void Context::Lock::addPendingBreakpoints(const std::string& filename, const std::vector<int>& lines)
+{
+	ctx->pendingBreakpoints.emplace(filename, lines);
+}
+
+bool Context::Lock::isFunctionBreakpoint(const std::string& name)
+{
+	return ctx->functionBreakpoints.count(name) > 0;
+}
+
+}  // namespace dbg
+}  // namespace vk
\ No newline at end of file
diff --git a/src/Vulkan/Debug/Context.hpp b/src/Vulkan/Debug/Context.hpp
new file mode 100644
index 0000000..dea6545
--- /dev/null
+++ b/src/Vulkan/Debug/Context.hpp
@@ -0,0 +1,161 @@
+// 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_CONTEXT_HPP_
+#define VK_DEBUG_CONTEXT_HPP_
+
+#include "ID.hpp"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace vk
+{
+namespace dbg
+{
+
+// Forward declarations.
+class Thread;
+class File;
+class Frame;
+class Scope;
+class VariableContainer;
+class EventListener;
+
+// Context holds the full state of the debugger, including all current files,
+// threads, frames and variables. It also holds a list of EventListeners that
+// can be broadcast to using the Context::broadcast() interface.
+// Context requires locking before accessing any state. The lock is
+// non-reentrant and careful use is required to prevent accidentical
+// double-locking by the same thread.
+class Context
+{
+	class Impl;
+
+public:
+	// Lock is the interface to the Context's state.
+	// The lock is automatically released when the Lock is destructed.
+	class Lock
+	{
+	public:
+		Lock(Impl*);
+		Lock(Lock&&);
+		~Lock();
+
+		// move-assignment operator.
+		Lock& operator=(Lock&&);
+
+		// unlock() explicitly unlocks before the Lock destructor is called.
+		// It is illegal to call any other methods after calling unlock().
+		void unlock();
+
+		// currentThread() creates (or returns an existing) a Thread that
+		// represents the currently executing thread.
+		std::shared_ptr<Thread> currentThread();
+
+		// get() returns the thread with the given ID, or null if the thread
+		// does not exist or no longer has any external shared_ptr references.
+		std::shared_ptr<Thread> get(ID<Thread>);
+
+		// threads() returns the full list of threads that still have an
+		// external shared_ptr reference.
+		std::vector<std::shared_ptr<Thread>> threads();
+
+		// createVirtualFile() returns a new file that is not backed by the
+		// filesystem.
+		// name is the unique name of the file.
+		// source is the content of the file.
+		std::shared_ptr<File> createVirtualFile(const std::string& name,
+		                                        const std::string& source);
+
+		// createPhysicalFile() returns a new file that is backed by the file
+		// at path.
+		std::shared_ptr<File> createPhysicalFile(const std::string& path);
+
+		// get() returns the file with the given ID, or null if the file
+		// does not exist or no longer has any external shared_ptr references.
+		std::shared_ptr<File> get(ID<File>);
+
+		// files() returns the full list of files.
+		std::vector<std::shared_ptr<File>> files();
+
+		// createFrame() returns a new frame for the given file.
+		std::shared_ptr<Frame> createFrame(
+		    const std::shared_ptr<File>& file);
+
+		// get() returns the frame with the given ID, or null if the frame
+		// does not exist or no longer has any external shared_ptr references.
+		std::shared_ptr<Frame> get(ID<Frame>);
+
+		// createScope() returns a new scope for the given file.
+		std::shared_ptr<Scope> createScope(
+		    const std::shared_ptr<File>& file);
+
+		// get() returns the scope with the given ID, or null if the scope
+		// does not exist.
+		std::shared_ptr<Scope> get(ID<Scope>);
+
+		// createVariableContainer() returns a new variable container.
+		std::shared_ptr<VariableContainer> createVariableContainer();
+
+		// 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>);
+
+		// addFunctionBreakpoint() adds a breakpoint to the start of the
+		// function with the given name.
+		void addFunctionBreakpoint(const std::string& name);
+
+		// addPendingBreakpoints() adds a number of breakpoints to the file with
+		// the given name which has not yet been created with a call to
+		// createVirtualFile() or createPhysicalFile().
+		void addPendingBreakpoints(const std::string& name, const std::vector<int>& lines);
+
+		// isFunctionBreakpoint() returns true if the function with the given
+		// name has a function breakpoint set.
+		bool isFunctionBreakpoint(const std::string& name);
+
+	private:
+		Lock(const Lock&) = delete;
+		Lock& operator=(const Lock&) = delete;
+		Impl* ctx;
+	};
+
+	// create() creates and returns a new Context.
+	static std::shared_ptr<Context> create();
+
+	virtual ~Context() = default;
+
+	// lock() returns a Lock which exclusively locks the context for state
+	// access.
+	virtual Lock lock() = 0;
+
+	// addListener() registers an EventListener for event notifications.
+	virtual void addListener(EventListener*) = 0;
+
+	// removeListener() unregisters an EventListener that was previously
+	// registered by a call to addListener().
+	virtual void removeListener(EventListener*) = 0;
+
+	// broadcast() returns an EventListener that will broadcast all methods on
+	// to all registered EventListeners.
+	virtual EventListener* broadcast() = 0;
+};
+
+}  // namespace dbg
+}  // namespace vk
+
+#endif  // VK_DEBUG_CONTEXT_HPP_