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_